summaryrefslogtreecommitdiff
path: root/gwm.c
diff options
context:
space:
mode:
authorGary Wong <gtw@gnu.org>2009-08-22 22:31:14 -0600
committerGary Wong <gtw@gnu.org>2009-08-22 22:31:14 -0600
commitf56f44b401baa435693efe497b0cda8749a279fe (patch)
treea59f7b711c8a5bdbe19f034d49123d7c0739100d /gwm.c
Initial commit (gwm-basic 1.0).
Diffstat (limited to 'gwm.c')
-rw-r--r--gwm.c2601
1 files changed, 2601 insertions, 0 deletions
diff --git a/gwm.c b/gwm.c
new file mode 100644
index 0000000..b5a0d75
--- /dev/null
+++ b/gwm.c
@@ -0,0 +1,2601 @@
+/*
+ * gwm.c
+ *
+ * Part of gwm, the Gratuitous Window Manager,
+ * by Gary Wong, <gtw@gnu.org>.
+ *
+ * Copyright (C) 2009 Gary Wong
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 3 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * $Id$
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <errno.h>
+#if HAVE_MCHECK_H
+#include <mcheck.h>
+#endif
+#if HAVE_POLL_H
+#include <poll.h>
+#endif
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <sys/wait.h>
+#if USE_SHAPE
+#include <xcb/shape.h>
+#endif
+#include <xcb/xcb.h>
+#include <xcb/xcbext.h>
+
+#include "gwm.h"
+
+#include "button.h"
+#include "decorate-core.h"
+#include "frame.h"
+#include "keyboard.h"
+#include "managed.h"
+#include "root.h"
+#include "utf8.h"
+#include "window-table.h"
+
+xcb_connection_t *c;
+xcb_timestamp_t latest_timestamp;
+
+xcb_atom_t atoms[ NUM_ATOMS ];
+
+static const char *atom_names[ NUM_ATOMS ] = {
+ "COMPOUND_TEXT",
+ "MANAGER",
+ "UTF8_STRING",
+ "VERSION",
+ "WM_CHANGE_STATE",
+ "WM_COLORMAP_NOTIFY",
+ "WM_COLORMAP_WINDOWS",
+ "WM_DELETE_WINDOW",
+ "WM_PROTOCOLS",
+ "WM_STATE",
+ "WM_TAKE_FOCUS"
+};
+
+xcb_atom_t prop_atoms[ NUM_PROPS ];
+xcb_atom_t prop_types[ NUM_PROPS ];
+
+static const xcb_extension_t *extensions[ EXTENSIONS_SIZE ] = {
+#if USE_SHAPE
+ &xcb_shape_id,
+#endif
+};
+
+int have_extension[ EXTENSIONS_SIZE ];
+uint8_t extension_event[ EXTENSIONS_SIZE ],
+ extension_error[ EXTENSIONS_SIZE ];
+
+int num_screens;
+xcb_screen_t **screens;
+struct gwm_screen *gwm_screens;
+
+xcb_cursor_t cursors[ NUM_CURSORS ];
+
+struct gwm_window *fake_window, *focus_frame;
+
+const char *argv0;
+static int flag_replace, flag_force;
+#if DEBUG
+static int flag_debug;
+#endif
+
+static volatile int signal_caught;
+
+static void ( *update_window )( struct gwm_window *window );
+
+static FORMAT( printf, 1, 2 ) void warning( char *format, ... ) {
+
+ va_list val;
+
+ va_start( val, format );
+
+ fprintf( stderr, "%s: warning: ", argv0 );
+ vfprintf( stderr, format, val );
+ putc( '\n', stderr );
+
+ va_end( val );
+}
+
+static FORMAT( printf, 1, 2 ) NORETURN void fatal( char *format, ... ) {
+
+ va_list val;
+
+ va_start( val, format );
+
+ fprintf( stderr, "%s: ", argv0 );
+ vfprintf( stderr, format, val );
+ putc( '\n', stderr );
+
+ va_end( val );
+
+ exit( 1 ); /* don't attempt an orderly shutdown */
+}
+
+extern MALLOC void *xmalloc( size_t size ) {
+
+ void *p;
+
+ /* It would be nice to clean up gracefully on allocation errors.
+ Unfortunately, doing so would require transmitting many requests
+ to the X server, which would require further allocations which
+ are likely to fail. Simply aborting with an error message is
+ therefore probably about as effective, and much simpler. */
+
+ if( !( p = malloc( size ) ) ) {
+ perror( argv0 );
+
+ exit( 1 );
+ }
+
+ return p;
+}
+
+extern void *xrealloc( void *p, size_t size ) {
+
+ if( !( p = realloc( p, size ) ) ) {
+ perror( argv0 );
+
+ exit( 1 );
+ }
+
+ return p;
+}
+
+extern MALLOC void *xcalloc( size_t number, size_t size ) {
+
+ void *p;
+
+ if( !( p = calloc( number, size ) ) ) {
+ perror( argv0 );
+
+ exit( 1 );
+ }
+
+ return p;
+}
+
+static void catch_signal( int n ) {
+
+ struct sigaction sa;
+
+ signal_caught = n;
+
+ sa.sa_handler = SIG_DFL;
+ sigemptyset( &sa.sa_mask );
+ sa.sa_flags = 0;
+ sigaction( SIGHUP, &sa, NULL );
+ sigaction( SIGINT, &sa, NULL );
+ sigaction( SIGTERM, &sa, NULL );
+}
+
+static void alarm_signal( int n ) {
+
+ signal_caught = n;
+}
+
+static void child_signal( int n ) {
+
+ while( waitpid( -1, 0, WNOHANG ) > 0 )
+ ;
+}
+
+static struct async_callback {
+ unsigned int sequence;
+ void ( *callback )( unsigned int sequence, void *reply,
+ xcb_generic_error_t *error, union callback_param p );
+ union callback_param p;
+ struct async_callback *next;
+} *queue_head, *queue_tail;
+
+static void check_async_callbacks( void ) {
+
+ void *reply;
+ xcb_generic_error_t *error;
+
+ while( queue_head && xcb_poll_for_reply( c, queue_head->sequence,
+ &reply, &error ) ) {
+ struct async_callback *entry = queue_head;
+
+ if( !( queue_head = entry->next ) )
+ queue_tail = NULL;
+
+ entry->callback( entry->sequence, reply, error, entry->p );
+
+ free( entry );
+ }
+}
+
+extern void sync_with_callback( unsigned int sequence ) {
+
+ while( queue_head && (int) sequence - (int) queue_head->sequence >= 0 ) {
+ struct async_callback *entry = queue_head;
+ xcb_generic_error_t *error;
+ void *reply = xcb_wait_for_reply( c, entry->sequence, &error );
+
+ if( !( queue_head = entry->next ) )
+ queue_tail = NULL;
+
+ entry->callback( entry->sequence, reply, error, entry->p );
+
+ free( entry );
+ }
+}
+
+#if DEBUG
+static void show_window( char *label, xcb_window_t w ) {
+
+ /* We don't want to use lookup_window here, since mere debugging
+ output shouldn't trigger the side effect of finishing incomplete
+ windows. */
+ struct gwm_window *window = table_lookup( &windows, w );
+
+ printf( " %s %08X (", label, w );
+
+ if( w == XCB_NONE )
+ fputs( "None", stdout );
+ else if( window ) {
+ struct gwm_window *client;
+ switch( window->type ) {
+ case WINDOW_ROOT:
+ printf( "root, screen %d", window->screen );
+ client = NULL;
+ break;
+
+ case WINDOW_MANAGED:
+ client = window;
+ break;
+
+ case WINDOW_FRAME:
+ fputs( "frame, ", stdout );
+ client = window->u.frame.child;
+ break;
+
+ case WINDOW_BUTTON:
+ fputs( "button, ", stdout );
+ client = window->u.button.frame->u.frame.child;
+ break;
+
+ case WINDOW_FAKE:
+ fputs( "fake", stdout );
+ client = NULL;
+ break;
+
+ case WINDOW_FEEDBACK:
+ printf( "feedback, screen %d", window->screen );
+ client = NULL;
+ break;
+
+ case WINDOW_INCOMPLETE:
+ fputs( "incomplete", stdout );
+ client = NULL;
+ break;
+ }
+
+ if( client )
+ fputs( client->u.managed.name ? client->u.managed.name : "unnamed",
+ stdout );
+ } else
+ fputs( "unknown", stdout );
+
+ putchar( ')' );
+ putchar( '\n' );
+}
+
+static void show_atom( char *label, xcb_atom_t atom ) {
+
+ static const char *predefined_names[ 69 ] = {
+ "None", "PRIMARY", "SECONDARY", "ARC", "ATOM", "BITMAP", "CARDINAL",
+ "COLORMAP", "CURSOR", "CUT_BUFFER0", "CUT_BUFFER1", "CUT_BUFFER2",
+ "CUT_BUFFER3", "CUT_BUFFER4", "CUT_BUFFER5", "CUT_BUFFER6",
+ "CUT_BUFFER7", "DRAWABLE", "FONT", "INTEGER", "PIXMAP", "POINT",
+ "RECTANGLE", "RESOURCE_MANAGER", "RGB_COLOR_MAP", "RGB_BEST_MAP",
+ "RGB_BLUE_MAP", "RGB_DEFAULT_MAP", "RGB_GRAY_MAP", "RGB_GREEN_MAP",
+ "RGB_RED_MAP", "STRING", "VISUALID", "WINDOW", "WM_COMMAND",
+ "WM_HINTS", "WM_CLIENT_MACHINE", "WM_ICON_NAME", "WM_ICON_SIZE",
+ "WM_NAME", "WM_NORMAL_HINTS", "WM_SIZE_HINTS", "WM_ZOOM_HINTS",
+ "MIN_SPACE", "NORM_SPACE", "MAX_SPACE", "END_SPACE", "SUPERSCRIPT_X",
+ "SUPERSCRIPT_Y", "SUBSCRIPT_X", "SUBSCRIPT_Y", "UNDERLINE_POSITION",
+ "UNDERLINE_THICKNESS", "STRIKEOUT_ASCENT", "STRIKEOUT_DESCENT",
+ "ITALIC_ANGLE", "X_HEIGHT", "QUAD_WIDTH", "WEIGHT", "POINT_SIZE",
+ "RESOLUTION", "COPYRIGHT", "NOTICE", "FONT_NAME", "FAMILY_NAME",
+ "FULL_NAME", "CAP_HEIGHT", "WM_CLASS", "WM_TRANSIENT_FOR"
+ };
+ const char *name;
+
+ if( atom < 69 )
+ name = predefined_names[ atom ];
+ else {
+ int i;
+
+ for( i = 0; i < NUM_ATOMS; i++ )
+ if( atoms[ i ] == atom ) {
+ name = atom_names[ i ];
+ break;
+ }
+
+ if( i == NUM_ATOMS )
+ name = "unknown";
+ }
+
+ printf( " %s %d (%s)\n", label, atom, name );
+}
+#endif
+
+extern void show_error( xcb_generic_error_t *error ) {
+
+ xcb_value_error_t *verror = (xcb_value_error_t *) error;
+#if DEBUG
+ static const char *type_strings[ XCB_IMPLEMENTATION + 1 ] = {
+ "Unknown", "Request", "Value", "Window", "Pixmap", "Atom", "Cursor",
+ "Font", "Match", "Drawable", "Access", "Alloc", "Colormap", "GContext",
+ "IDChoice", "Name", "Length", "Implementation"
+ };
+ const char *type, *bad_value_str;
+ char num[ 32 ], bad_num[ 32 ];
+
+ if( error->error_code <= XCB_IMPLEMENTATION )
+ type = type_strings[ error->error_code ];
+ else {
+ sprintf( num, "%d", error->error_code );
+ type = num;
+ }
+
+ switch( error->error_code ) {
+ case XCB_VALUE:
+ case XCB_WINDOW:
+ case XCB_PIXMAP:
+ case XCB_ATOM:
+ case XCB_CURSOR:
+ case XCB_FONT:
+ case XCB_DRAWABLE:
+ case XCB_COLORMAP:
+ case XCB_G_CONTEXT:
+ case XCB_ID_CHOICE:
+ sprintf( bad_num, ", val %X", verror->bad_value );
+ bad_value_str = bad_num;
+ break;
+
+ default:
+ bad_value_str = "";
+ break;
+ }
+
+ warning( "unexpected error (type %s, sequence %d; opcode %d %d%s)", type,
+ error->sequence, verror->major_opcode, verror->minor_opcode,
+ bad_value_str );
+#else
+ warning( "unexpected error (type %d, sequence %d; opcode %d %d)",
+ error->error_code, error->sequence,
+ verror->major_opcode, verror->minor_opcode );
+#endif
+}
+
+#if DEBUG
+static void show_event( xcb_generic_event_t *generic ) {
+
+ static const char *event_names[ XCB_MAPPING_NOTIFY + 1 ] = {
+ "Error", "Reply", "KeyPress", "KeyRelease", "ButtonPress",
+ "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify",
+ "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExposure",
+ "NoExposure", "VisibilityNotify", "CreateNotify", "DestroyNotify",
+ "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify",
+ "ConfigureNotify", "ConfigureRequest", "GravityNotify",
+ "ResizeRequest", "CirculateNotify", "CirculateRequest",
+ "PropertyNotify", "SelectionClear", "SelectionRequest",
+ "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify"
+ };
+ union event {
+ xcb_key_press_event_t kbm; /* KeyPress, KeyRelease, ButtonPress,
+ ButtonRelease, MotionNotify */
+ xcb_enter_notify_event_t el; /* EnterNotify, LeaveNotify */
+ xcb_focus_in_event_t f; /* FocusIn, FocusOut */
+ xcb_keymap_notify_event_t km; /* KeymapNotify */
+ xcb_expose_event_t e; /* Expose */
+ xcb_graphics_exposure_event_t g; /* GraphicsExposure */
+ xcb_no_exposure_event_t n; /* NoExposure */
+ xcb_visibility_notify_event_t v; /* VisibilityNotify */
+ xcb_create_notify_event_t c; /* CreateNotify */
+ xcb_destroy_notify_event_t d; /* DestroyNotify */
+ xcb_unmap_notify_event_t u; /* UnmapNotify */
+ xcb_map_notify_event_t m; /* MapNotify */
+ xcb_map_request_event_t mr; /* MapRequest */
+ xcb_reparent_notify_event_t r; /* ReparentNotify */
+ xcb_configure_notify_event_t con; /* ConfigureNotify */
+ xcb_configure_request_event_t cr; /* ConfigureRequest */
+ xcb_gravity_notify_event_t gn; /* GravityNotify */
+ xcb_resize_request_event_t rr; /* ResizeRequest */
+ xcb_circulate_notify_event_t cir; /* CirculateNotify */
+ xcb_circulate_request_event_t cirr; /* CirculateRequest */
+ xcb_property_notify_event_t p; /* PropertyNotify */
+ xcb_selection_clear_event_t sc; /* SelectionClear */
+ xcb_selection_request_event_t sr; /* SelectionRequest */
+ xcb_selection_notify_event_t sn; /* SelectionNotify */
+ xcb_colormap_notify_event_t cm; /* ColormapNotify */
+ xcb_client_message_event_t cli; /* ClientMessage */
+ xcb_mapping_notify_event_t map; /* MappingNotify */
+ } *ev = (union event *) generic;
+ int type = generic->response_type & 0x7F;
+ int i;
+
+ if( !( generic->response_type & ~SEND_EVENT_MASK ) ) {
+ show_error( (xcb_generic_error_t *) generic );
+
+ return;
+ }
+
+ printf( "Event %d (%s%s)\n", generic->response_type,
+ type <= XCB_MAPPING_NOTIFY ? event_names[ type ] : "Unknown",
+ generic->response_type & SEND_EVENT_MASK ? ", synthetic" : "" );
+
+ if( type < XCB_KEY_PRESS || type > XCB_MAPPING_NOTIFY )
+ return;
+
+ if( type != XCB_KEYMAP_NOTIFY )
+ printf( " Sequence %X\n", generic->full_sequence );
+
+ switch( type ) {
+ case XCB_KEY_PRESS:
+ case XCB_KEY_RELEASE:
+ case XCB_BUTTON_PRESS:
+ case XCB_BUTTON_RELEASE:
+ case XCB_MOTION_NOTIFY:
+ show_window( "Root", ev->kbm.root );
+ show_window( "Event", ev->kbm.event );
+ show_window( "Child", ev->kbm.child );
+ printf( " Same-screen %s\n"
+ " Root x/y %d, %d\n"
+ " Event x/y %d, %d\n"
+ " Detail %d\n"
+ " State %04X\n"
+ " Time %d\n",
+ ev->kbm.same_screen ? "Yes" : "No",
+ ev->kbm.root_x, ev->kbm.root_y,
+ ev->kbm.event_x, ev->kbm.event_y,
+ ev->kbm.detail, ev->kbm.state, ev->kbm.time );
+ break;
+
+ case XCB_ENTER_NOTIFY:
+ case XCB_LEAVE_NOTIFY:
+ show_window( "Root", ev->el.root );
+ show_window( "Event", ev->el.event );
+ show_window( "Child", ev->el.child );
+ printf( " Same-screen %s\n"
+ " Root x/y %d, %d\n"
+ " Event x/y %d, %d\n"
+ " Mode %d\n"
+ " Detail %d\n"
+ " Focus %d\n"
+ " State %04X\n"
+ " Time %d\n",
+ ev->el.same_screen_focus & 2 ? "Yes" : "No",
+ ev->el.root_x, ev->el.root_y,
+ ev->el.event_x, ev->el.event_y,
+ ev->el.mode, ev->el.detail, ev->el.same_screen_focus & 1,
+ ev->el.state, ev->el.time );
+ break;
+
+ case XCB_FOCUS_IN:
+ case XCB_FOCUS_OUT:
+ show_window( "Event", ev->f.event );
+ printf( " Mode %d\n"
+ " Detail %d\n",
+ ev->f.mode, ev->f.detail );
+
+ case XCB_KEYMAP_NOTIFY:
+ for( i = 0; i < 0xF8; i++ )
+ if( ev->km.keys[ i >> 3 ] & ( 1 << ( i & 7 ) ) )
+ printf( " key %d down\n", i );
+
+ break;
+
+ case XCB_EXPOSE:
+ show_window( "Window", ev->e.window );
+ printf( " x/y %d, %d\n"
+ " width/height %d x %d\n"
+ " Count %d\n",
+ ev->e.x, ev->e.y, ev->e.width, ev->e.height, ev->e.count );
+ break;
+
+ case XCB_GRAPHICS_EXPOSURE:
+ show_window( "Drawable", ev->g.drawable );
+ printf( " x/y %d, %d\n"
+ " width/height %d x %d\n"
+ " Count %d\n"
+ " Opcode %d, %d\n",
+ ev->g.x, ev->g.y, ev->g.width, ev->g.height, ev->g.count,
+ ev->g.major_opcode, ev->g.minor_opcode );
+ break;
+
+ case XCB_NO_EXPOSURE:
+ show_window( "Drawable", ev->n.drawable );
+ printf( " Opcode %d, %d\n",
+ ev->n.major_opcode, ev->n.minor_opcode );
+ break;
+
+ case XCB_VISIBILITY_NOTIFY:
+ show_window( "Window", ev->v.window );
+ printf( " State %d\n", ev->v.state );
+ break;
+
+ case XCB_CREATE_NOTIFY:
+ show_window( "Parent", ev->c.parent );
+ show_window( "Window", ev->c.window );
+ printf( " x/y %d, %d\n"
+ " width/height %d x %d\n"
+ " Border width %d\n"
+ " Override redirect %s\n",
+ ev->c.x, ev->c.y,
+ ev->c.width, ev->c.height,
+ ev->c.border_width,
+ ev->c.override_redirect ? "Yes" : "No" );
+ break;
+
+ case XCB_DESTROY_NOTIFY:
+ show_window( "Event", ev->d.event );
+ show_window( "Window", ev->d.window );
+ break;
+
+ case XCB_UNMAP_NOTIFY:
+ show_window( "Event", ev->u.event );
+ show_window( "Window", ev->u.window );
+ printf( " From configure: %s\n",
+ ev->u.from_configure ? "Yes" : "No" );
+ break;
+
+ case XCB_MAP_NOTIFY:
+ show_window( "Event", ev->m.event );
+ show_window( "Window", ev->m.window );
+ printf( " Override redirect: %s\n",
+ ev->m.override_redirect ? "Yes" : "No" );
+ break;
+
+ case XCB_MAP_REQUEST:
+ show_window( "Parent", ev->mr.parent );
+ show_window( "Window", ev->mr.window );
+ break;
+
+ case XCB_REPARENT_NOTIFY:
+ show_window( "Event", ev->r.event );
+ show_window( "Window", ev->r.window );
+ show_window( "Parent", ev->r.parent );
+ printf( " x/y %d, %d\n"
+ " Override redirect %s\n",
+ ev->r.x, ev->r.y,
+ ev->r.override_redirect ? "Yes" : "No" );
+ break;
+
+ case XCB_CONFIGURE_NOTIFY:
+ show_window( "Event", ev->con.event );
+ show_window( "Window", ev->con.window );
+ printf( " x/y %d, %d\n"
+ " width/height %d x %d\n"
+ " Border width %d\n",
+ ev->con.x, ev->con.y,
+ ev->con.width, ev->con.height,
+ ev->con.border_width );
+ show_window( "Above sibling", ev->con.above_sibling );
+ printf( " Override redirect %s\n",
+ ev->con.override_redirect ? "Yes" : "No" );
+ break;
+
+ case XCB_CONFIGURE_REQUEST:
+ show_window( "Parent", ev->cr.parent );
+ show_window( "Window", ev->cr.window );
+ printf( " x/y %d, %d\n"
+ " width/height %d x %d\n"
+ " Border width %d\n",
+ ev->cr.x, ev->cr.y,
+ ev->cr.width, ev->cr.height,
+ ev->cr.border_width );
+ show_window( "Sibling", ev->cr.sibling );
+ printf( " Stack mode %d\n"
+ " Value mask %04X\n",
+ ev->cr.stack_mode, ev->cr.value_mask );
+ break;
+
+ case XCB_GRAVITY_NOTIFY:
+ show_window( "Event", ev->gn.event );
+ show_window( "Window", ev->gn.window );
+ printf( " x/y %d, %d\n",
+ ev->gn.x, ev->gn.y );
+ break;
+
+ case XCB_RESIZE_REQUEST:
+ show_window( "Window", ev->rr.window );
+ printf( " width/height %d x %d\n",
+ ev->rr.width, ev->rr.height );
+ break;
+
+ case XCB_CIRCULATE_NOTIFY:
+ show_window( "Event", ev->cir.event );
+ show_window( "Window", ev->cir.window );
+ printf( " Place %d\n", ev->cir.place );
+ break;
+
+ case XCB_CIRCULATE_REQUEST:
+ show_window( "Parent", ev->cirr.event );
+ show_window( "Window", ev->cirr.window );
+ printf( " Place %d\n", ev->cirr.place );
+ break;
+
+ case XCB_PROPERTY_NOTIFY:
+ show_window( "Window", ev->p.window );
+ show_atom( "Atom", ev->p.atom );
+ printf( " State %d\n"
+ " Time %d\n",
+ ev->p.state, ev->p.time );
+ break;
+
+ case XCB_SELECTION_CLEAR:
+ show_window( "Owner", ev->sc.owner );
+ show_atom( "Selection", ev->sc.selection );
+ printf( " Time %d\n", ev->sc.time );
+ break;
+
+ case XCB_SELECTION_REQUEST:
+ show_window( "Owner", ev->sr.owner );
+ show_atom( "Selection", ev->sr.selection );
+ show_atom( "Target", ev->sr.target );
+ show_atom( "Property", ev->sr.property );
+ show_window( "Requestor", ev->sr.requestor );
+ printf( " Time %d\n", ev->sr.time );
+ break;
+
+ case XCB_SELECTION_NOTIFY:
+ show_window( "Requestor", ev->sn.requestor );
+ show_atom( "Selection", ev->sn.selection );
+ show_atom( "Target", ev->sn.target );
+ show_atom( "Property", ev->sn.property );
+ printf( " Time %d\n", ev->sn.time );
+ break;
+
+ case XCB_COLORMAP_NOTIFY:
+ show_window( "Window", ev->cm.window );
+ printf( " Colormap %d\n"
+ " New %s\n"
+ " State %d\n",
+ ev->cm.colormap,
+ ev->cm._new ? "Yes" : "No",
+ ev->cm.state );
+ break;
+
+ case XCB_CLIENT_MESSAGE:
+ show_window( "Window", ev->cli.window );
+ show_atom( "Type", ev->cli.type );
+ printf( " Format %d\n", ev->cli.format );
+
+ switch( ev->cli.format ) {
+ case 8:
+ for( i = 0; i < 20; i++ )
+ printf( " %2d %02X\n", i, ev->cli.data.data8[ i ] );
+ break;
+
+ case 16:
+ for( i = 0; i < 10; i++ )
+ printf( " %d %04X\n", i, ev->cli.data.data16[ i ] );
+ break;
+
+ case 32:
+ for( i = 0; i < 5; i++ )
+ printf( " %d %08X\n", i, ev->cli.data.data32[ i ] );
+ break;
+ }
+
+ break;
+
+ case XCB_MAPPING_NOTIFY:
+ printf( " Request %d\n", ev->map.request );
+ if( ev->map.request == XCB_MAPPING_KEYBOARD )
+ printf( " First keycode %d\n"
+ " Count %d",
+ ev->map.first_keycode, ev->map.count );
+ break;
+ }
+}
+#endif
+
+extern xcb_generic_event_t *wait_for_event( void ) {
+
+ xcb_generic_event_t *ev;
+ sigset_t sigs, oldsigs;
+ int fd = xcb_get_file_descriptor( c );
+#if HAVE_PPOLL
+ struct pollfd pfd;
+#else
+ fd_set fds;
+#endif
+
+ for(;;) {
+ if( xcb_connection_has_error( c ) )
+ return NULL;
+
+ ev = xcb_poll_for_event( c );
+
+#if DEBUG
+ if( flag_debug && ev )
+ show_event( ev );
+#endif
+
+ check_async_callbacks(); /* must poll for events first, since
+ polling replies won't dequeue them */
+ if( ev )
+ return ev;
+
+ if( update_windows.used ) {
+ struct gwm_window *update = update_windows.values[ 0 ];
+
+ update_window( update );
+ window_update_done( update );
+
+ continue;
+ }
+
+ xcb_flush( c );
+
+ sigemptyset( &sigs );
+ sigaddset( &sigs, SIGHUP );
+ sigaddset( &sigs, SIGINT );
+ sigaddset( &sigs, SIGTERM );
+
+ sigprocmask( SIG_BLOCK, &sigs, &oldsigs );
+
+ if( signal_caught ) {
+ sigprocmask( SIG_SETMASK, &oldsigs, NULL );
+
+ return NULL;
+ }
+
+#if HAVE_PPOLL
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+
+ if( ppoll( &pfd, 1, NULL, &oldsigs ) < 0 && errno != EINTR )
+ perror( "ppoll" );
+#else
+ FD_ZERO( &fds );
+ FD_SET( fd, &fds );
+
+ if( pselect( fd + 1, &fds, NULL, NULL, NULL, &oldsigs ) < 0 &&
+ errno != EINTR )
+ perror( "pselect" );
+#endif
+
+ sigprocmask( SIG_SETMASK, &oldsigs, NULL );
+ }
+}
+
+extern void handle_async_reply( unsigned int sequence,
+ void ( *callback )( unsigned int sequence,
+ void *reply,
+ xcb_generic_error_t *error,
+ union callback_param p ),
+ union callback_param p ) {
+
+ struct async_callback *entry = xmalloc( sizeof *entry );
+
+ entry->sequence = sequence;
+ entry->callback = callback;
+ entry->p = p;
+ entry->next = NULL;
+
+ if( queue_tail )
+ queue_tail = queue_tail->next = entry;
+ else
+ queue_head = queue_tail = entry;
+}
+
+static void error_callback( unsigned int sequence, void *reply,
+ xcb_generic_error_t *error,
+ union callback_param p ) {
+
+ unsigned ignore = p.l;
+
+ if( reply )
+ free( reply );
+
+ if( !error )
+ return; /* no error */
+
+ if( !( ignore & ( 1 << ( error->error_code - 1 ) ) ) )
+ show_error( error ); /* an error we didn't expect */
+
+ free( error );
+}
+
+extern void handle_error_reply( xcb_void_cookie_t cookie, unsigned ignore ) {
+
+ union callback_param p;
+
+ p.l = ignore;
+
+ handle_async_reply( cookie.sequence, error_callback, p );
+}
+
+static void install_colormap( int screen, xcb_colormap_t cmap,
+ xcb_timestamp_t t ) {
+
+ if( (int) t - (int) gwm_screens[ screen ].cmap_time > 0 ) {
+ gwm_screens[ screen ].cmap_time = t;
+
+ if( gwm_screens[ screen ].cmap != cmap )
+ xcb_install_colormap( c, gwm_screens[ screen ].cmap = cmap );
+ }
+}
+
+struct cmap_callback {
+ int screen;
+ xcb_timestamp_t t;
+};
+
+static void handle_get_window_attributes( unsigned int sequence, void *reply,
+ xcb_generic_error_t *error,
+ union callback_param cp ) {
+
+ struct cmap_callback *cc = cp.p;
+
+ if( error ) {
+ /* Ignore Window errors, since we were given the ID by a client
+ and we don't trust them to get it right. */
+ if( error->error_code != XCB_WINDOW )
+ show_error( error );
+
+ free( error );
+ }
+
+ if( reply ) {
+ xcb_get_window_attributes_reply_t *r = reply;
+
+ install_colormap( cc->screen, r->colormap ? r->colormap :
+ screens[ cc->screen ]->default_colormap, cc->t );
+
+ free( reply );
+ }
+
+ free( cc );
+}
+
+/* Install a window's colormap on a screen, or the default colormap if
+ unspecified. */
+extern void install_window_colormap( int screen, struct gwm_window *window,
+ xcb_timestamp_t t ) {
+
+ assert( !window || window->type == WINDOW_MANAGED );
+
+ if( window && window->u.managed.cmap_window ) {
+ union callback_param cp;
+ struct cmap_callback *cc = xmalloc( sizeof *cc );
+
+ cc->screen = screen;
+ cc->t = t;
+ cp.p = cc;
+ handle_async_reply( xcb_get_window_attributes(
+ c, window->u.managed.cmap_window ).sequence,
+ handle_get_window_attributes, cp );
+ } else
+ install_colormap( screen, window && window->u.managed.cmap ?
+ window->u.managed.cmap :
+ screens[ screen ]->default_colormap, t );
+}
+
+/* Return TRUE iff an event is the only button pressed (and so would
+ initiate a passive grab). */
+extern CONST int initial_press( xcb_button_press_event_t *ev ) {
+
+ return !( ev->state & 0x1F00 );
+}
+
+/* Return TRUE iff an event is the release of the last button (and so would
+ terminate a passive grab). */
+extern CONST int final_release( xcb_button_release_event_t *ev ) {
+
+ return ( ev->state & 0x1F00 ) == ( 0x80 << ev->detail );
+}
+
+extern void translate_child_to_frame( int *fx, int *fy, int *fwidth,
+ int *fheight, int cx, int cy,
+ int cwidth, int cheight,
+ int cborder, int win_gravity ) {
+
+ *fwidth = cwidth + FRAME_BORDER_WIDTH * 2;
+ *fheight = cheight + FRAME_BORDER_WIDTH + FRAME_TITLE_HEIGHT;
+
+ switch( win_gravity ) {
+ case XCB_GRAVITY_NORTH_WEST:
+ case XCB_GRAVITY_WEST:
+ case XCB_GRAVITY_SOUTH_WEST:
+ default:
+ *fx = cx;
+ break;
+
+ case XCB_GRAVITY_NORTH:
+ case XCB_GRAVITY_CENTER:
+ case XCB_GRAVITY_SOUTH:
+ case XCB_GRAVITY_STATIC:
+ *fx = cx + cborder - FRAME_BORDER_WIDTH - FRAME_X_BORDER;
+ break;
+
+ case XCB_GRAVITY_NORTH_EAST:
+ case XCB_GRAVITY_EAST:
+ case XCB_GRAVITY_SOUTH_EAST:
+ *fx = cx + ( ( cborder - FRAME_BORDER_WIDTH - FRAME_X_BORDER ) << 1 );
+ break;
+ }
+
+ switch( win_gravity ) {
+ case XCB_GRAVITY_NORTH_WEST:
+ case XCB_GRAVITY_NORTH:
+ case XCB_GRAVITY_NORTH_EAST:
+ default:
+ *fy = cy;
+ break;
+
+ case XCB_GRAVITY_WEST:
+ case XCB_GRAVITY_CENTER:
+ case XCB_GRAVITY_EAST:
+ *fy = cy + cborder - ( ( FRAME_BORDER_WIDTH +
+ FRAME_TITLE_HEIGHT ) >> 1 ) - FRAME_X_BORDER;
+ break;
+
+ case XCB_GRAVITY_SOUTH_WEST:
+ case XCB_GRAVITY_SOUTH:
+ case XCB_GRAVITY_SOUTH_EAST:
+ *fy = cy + ( cborder << 1 ) - FRAME_TITLE_HEIGHT - FRAME_BORDER_WIDTH -
+ ( FRAME_X_BORDER << 1 );
+ break;
+
+ case XCB_GRAVITY_STATIC:
+ *fy = cy + cborder - FRAME_TITLE_HEIGHT - FRAME_X_BORDER;
+ break;
+ }
+}
+
+extern void translate_frame_to_child( int *cx, int *cy, int fx, int fy,
+ int cborder, int win_gravity ) {
+
+ switch( win_gravity ) {
+ case XCB_GRAVITY_NORTH_WEST:
+ case XCB_GRAVITY_WEST:
+ case XCB_GRAVITY_SOUTH_WEST:
+ default:
+ *cx = fx;
+ break;
+
+ case XCB_GRAVITY_NORTH:
+ case XCB_GRAVITY_CENTER:
+ case XCB_GRAVITY_SOUTH:
+ case XCB_GRAVITY_STATIC:
+ *cx = fx - cborder + FRAME_BORDER_WIDTH + FRAME_X_BORDER;
+ break;
+
+ case XCB_GRAVITY_NORTH_EAST:
+ case XCB_GRAVITY_EAST:
+ case XCB_GRAVITY_SOUTH_EAST:
+ *cx = fx + ( ( FRAME_BORDER_WIDTH + FRAME_X_BORDER - cborder ) << 1 );
+ break;
+ }
+
+ switch( win_gravity ) {
+ case XCB_GRAVITY_NORTH_WEST:
+ case XCB_GRAVITY_NORTH:
+ case XCB_GRAVITY_NORTH_EAST:
+ default:
+ *cy = fy;
+ break;
+
+ case XCB_GRAVITY_WEST:
+ case XCB_GRAVITY_CENTER:
+ case XCB_GRAVITY_EAST:
+ *cy = fy - cborder + ( ( FRAME_BORDER_WIDTH +
+ FRAME_TITLE_HEIGHT ) >> 1 ) + FRAME_X_BORDER;
+ break;
+
+ case XCB_GRAVITY_SOUTH_WEST:
+ case XCB_GRAVITY_SOUTH:
+ case XCB_GRAVITY_SOUTH_EAST:
+ *cy = fy - ( cborder << 1 ) + FRAME_TITLE_HEIGHT + FRAME_BORDER_WIDTH +
+ ( FRAME_X_BORDER << 1 );
+ break;
+
+ case XCB_GRAVITY_STATIC:
+ *cy = fy - cborder + FRAME_TITLE_HEIGHT + FRAME_X_BORDER;
+ break;
+ }
+}
+
+extern void apply_size_constraints( struct gwm_window *window, int *width,
+ int *height ) {
+
+ int eff_base_width = window->u.managed.base_width ?
+ window->u.managed.base_width : window->u.managed.min_width,
+ eff_base_height = window->u.managed.base_height ?
+ window->u.managed.base_height : window->u.managed.min_height;
+
+ /* Apply the minimum and maximum constraints. These are already known
+ to be compatible. */
+ if( *width < window->u.managed.min_width )
+ *width = window->u.managed.min_width;
+
+ if( *height < window->u.managed.min_height )
+ *height = window->u.managed.min_height;
+
+ if( *width > window->u.managed.max_width )
+ *width = window->u.managed.max_width;
+
+ if( *height > window->u.managed.max_height )
+ *height = window->u.managed.max_height;
+
+ /* Now round down each dimension to an integer multiple of increments.
+ Rounding down cannot violate the maximum constraint, and since
+ eff_base_* >= min_*, it will not reduce below the minimum constraint. */
+ *width -= ( *width - eff_base_width ) % window->u.managed.width_inc;
+ *height -= ( *height - eff_base_height ) % window->u.managed.height_inc;
+
+ if( window->u.managed.min_aspect_x * *height >
+ window->u.managed.min_aspect_y * *width ) {
+ /* Minimum aspect ratio violated. Attempt to either increase the
+ width or decrease the height (whichever is a smaller change), but
+ don't do either if it would go outside the min/max bounds.
+ Both division operations are safe (min_aspect_y is always
+ positive, and min_aspect_x must be positive if there is a
+ violation). Note that an exact solution might not be possible
+ (e.g. certain cases where the aspect ratio and increments are
+ coprime). */
+ int min_x, max_y;
+
+ min_x = ( window->u.managed.min_aspect_x * *height +
+ ( window->u.managed.min_aspect_y - 1 ) ) /
+ window->u.managed.min_aspect_y + window->u.managed.width_inc - 1;
+ min_x -= ( min_x - eff_base_width ) % window->u.managed.width_inc;
+
+ max_y = window->u.managed.min_aspect_y * *width /
+ window->u.managed.min_aspect_x;
+ max_y -= ( max_y - eff_base_height ) % window->u.managed.height_inc;
+
+ if( min_x - *width < *height - max_y ) {
+ /* The width change is smaller: prefer it if possible. */
+ if( min_x >= window->u.managed.min_width )
+ *width = min_x;
+ else if( max_y < window->u.managed.max_height )
+ *height = max_y;
+ } else {
+ /* The height change is smaller: prefer it if possible. */
+ if( max_y < window->u.managed.max_height )
+ *height = max_y;
+ else if( min_x >= window->u.managed.min_width )
+ *width = min_x;
+ }
+ }
+
+ if( window->u.managed.max_aspect_x * *height <
+ window->u.managed.max_aspect_y * *width ) {
+ /* Maximum aspect ratio violated. Much like the case above... */
+ int min_y, max_x;
+
+ min_y = ( window->u.managed.max_aspect_y * *width +
+ ( window->u.managed.max_aspect_x - 1 ) ) /
+ window->u.managed.max_aspect_x + window->u.managed.height_inc - 1;
+ min_y -= ( min_y - eff_base_height ) % window->u.managed.height_inc;
+
+ max_x = window->u.managed.max_aspect_x * *height /
+ window->u.managed.max_aspect_y;
+ max_x -= ( max_x - eff_base_width ) % window->u.managed.width_inc;
+
+ if( min_y - *height < *width - max_x ) {
+ /* The height change is smaller: prefer it if possible. */
+ if( min_y >= window->u.managed.min_height )
+ *height = min_y;
+ else if( max_x < window->u.managed.max_width )
+ *width = max_x;
+ } else {
+ /* The width change is smaller: prefer it if possible. */
+ if( max_x < window->u.managed.max_width )
+ *width = max_x;
+ else if( min_y >= window->u.managed.min_height )
+ *height = min_y;
+ }
+ }
+}
+
+static void stop_listening( struct gwm_window *window ) {
+
+ uint32_t value;
+
+ /* Ignore Window errors on the child, because it might be destroyed
+ before we are notified. */
+
+ value = 0;
+ handle_error_reply( xcb_change_window_attributes_checked(
+ c, window->w, XCB_CW_EVENT_MASK, &value ),
+ ERR_MASK_WINDOW );
+
+#if USE_SHAPE
+ if( have_extension[ EXT_SHAPE ] )
+ handle_error_reply( xcb_shape_select_input_checked( c, window->w,
+ FALSE ),
+ ERR_MASK_WINDOW );
+#endif
+}
+
+static void set_managed_state( struct gwm_window *window, int state ) {
+
+ uint32_t values[ 2 ];
+
+ assert( window->type == WINDOW_MANAGED );
+
+ /* Place a WM_STATE property on the client window (ICCCM 2.0, section
+ 4.1.3.1). */
+ values[ 0 ] = window->u.managed.state = state;
+ values[ 1 ] = XCB_NONE;
+ xcb_change_property( c, XCB_PROP_MODE_REPLACE, window->w,
+ atoms[ ATOM_WM_STATE ], atoms[ ATOM_WM_STATE ],
+ 32, 2, values );
+}
+
+static void place_window( struct gwm_window *window, int *x, int *y,
+ int width, int height ) {
+
+ int sx = screens[ window->screen ]->width_in_pixels - width;
+ int sy = screens[ window->screen ]->height_in_pixels - height;
+ unsigned long long hash;
+
+ if( sx <= 0 )
+ *x = 0;
+ else {
+ hash = window->w * 0x9B4A36D1;
+ hash ^= hash >> 32;
+ *x = hash % sx;
+ }
+
+ if( sy <= 0 )
+ *y = 0;
+ else {
+ hash = window->w * 0x9B4A36D1;
+ hash ^= hash >> 32;
+ *y = hash % sy;
+ }
+}
+
+static void start_managing_window( struct gwm_window *window,
+ xcb_get_window_attributes_reply_t *attr,
+ xcb_get_geometry_reply_t *geom,
+#if USE_SHAPE
+ xcb_shape_query_extents_reply_t *shape,
+#endif
+ xcb_get_property_reply_t *props[],
+ int map_request ) {
+
+ int i;
+ uint32_t values[ 4 ];
+ struct gwm_window *frame, *button;
+ xcb_window_t w = window->w;
+
+ frame = add_window( xcb_generate_id( c ) );
+ button = add_window( xcb_generate_id( c ) );
+ window->screen = lookup_window( geom->root )->screen;
+ window->type = WINDOW_MANAGED;
+ window->u.managed.frame = frame;
+ window->u.managed.border_width = geom->border_width;
+ window->u.managed.cmap = attr->colormap;
+ window->u.managed.hints = 0;
+ window->u.managed.name = NULL;
+ window->u.managed.state = STATE_WITHDRAWN;
+ frame->screen = window->screen;
+ frame->type = WINDOW_FRAME;
+ frame->u.frame.child = window;
+ frame->u.frame.button = button;
+ button->screen = window->screen;
+ button->type = WINDOW_BUTTON;
+ button->u.button.frame = frame;
+
+ for( i = 0; i < NUM_PROPS; i++ )
+ managed_property_change( window, i, props[ i ] );
+
+ translate_child_to_frame( &frame->u.frame.x, &frame->u.frame.y,
+ &frame->u.frame.width, &frame->u.frame.height,
+ geom->x, geom->y, geom->width, geom->height,
+ geom->border_width,
+ window->u.managed.win_gravity );
+
+ if( map_request && !( window->u.managed.hints & HINT_POSITION ) ) {
+ int x, y;
+
+ place_window( window, &x, &y,
+ frame->u.frame.width, frame->u.frame.height );
+
+ frame->u.frame.x = x;
+ frame->u.frame.y = y;
+ }
+
+ /* Don't create frames entirely off-screen; ensure that at least 8 pixels
+ are within the root. */
+ if( frame->u.frame.x > screens[ frame->screen ]->width_in_pixels - 8 &&
+ frame->u.frame.x + frame->u.frame.width >
+ screens[ frame->screen ]->width_in_pixels )
+ frame->u.frame.x = screens[ frame->screen ]->width_in_pixels - 8;
+ else if( frame->u.frame.x < 0 &&
+ frame->u.frame.x + frame->u.frame.width < 8 )
+ frame->u.frame.x = 8 - frame->u.frame.width;
+
+ if( frame->u.frame.y > screens[ frame->screen ]->height_in_pixels - 8 &&
+ frame->u.frame.y + frame->u.frame.height >
+ screens[ frame->screen ]->height_in_pixels )
+ frame->u.frame.y = screens[ frame->screen ]->height_in_pixels - 8;
+ else if( frame->u.frame.y < 0 &&
+ frame->u.frame.y + frame->u.frame.height < 8 )
+ frame->u.frame.y = 8 - frame->u.frame.height;
+
+ values[ 0 ] = gwm_screens[ frame->screen ].pixels[
+ COL_FRAME_INACTIVE ]; /* background pixel */
+ values[ 1 ] = gwm_screens[ frame->screen ].pixels[
+ COL_BORDER ]; /* border pixel */
+ values[ 2 ] = XCB_GRAVITY_NORTH_WEST; /* bit gravity */
+ values[ 3 ] = TRUE; /* override redirect */
+ values[ 4 ] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
+ XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_POINTER_MOTION_HINT |
+ XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_EXPOSURE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
+
+ xcb_create_window( c, XCB_COPY_FROM_PARENT, frame->w, geom->root,
+ frame->u.frame.x, frame->u.frame.y,
+ frame->u.frame.width, frame->u.frame.height, 1,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
+ XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
+ XCB_CW_BIT_GRAVITY | XCB_CW_OVERRIDE_REDIRECT |
+ XCB_CW_EVENT_MASK, values );
+
+ values[ 0 ] = gwm_screens[ frame->screen ].pixels[
+ COL_BUTTON_INACTIVE ]; /* background pixel */
+ values[ 1 ] = gwm_screens[ frame->screen ].pixels[
+ COL_BORDER ]; /* border pixel */
+ values[ 2 ] = TRUE; /* override redirect */
+ values[ 3 ] = XCB_EVENT_MASK_EXPOSURE;
+ xcb_create_window( c, XCB_COPY_FROM_PARENT, button->w, frame->w,
+ 2, 1, FRAME_BUTTON_SIZE, FRAME_BUTTON_SIZE, 1,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
+ XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
+ XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, values );
+
+ xcb_grab_button( c, FALSE, button->w, XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE |
+ XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE,
+ XCB_NONE, XCB_BUTTON_INDEX_ANY, 0x8000 );
+
+ xcb_map_window( c, button->w );
+
+ xcb_change_save_set( c, XCB_SET_MODE_INSERT, w );
+
+ if( !map_request ) {
+ /* If the window was mapped already, then reparenting it will
+ generate an UnmapNotify. To avoid race conditions in discarding
+ this unwanted event, temporarily reduce our event mask. */
+ xcb_grab_server( c );
+
+ values[ 0 ] = XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_COLOR_MAP_CHANGE;
+ xcb_change_window_attributes( c, w, XCB_CW_EVENT_MASK, values );
+ }
+
+ xcb_reparent_window( c, w, frame->w, FRAME_BORDER_WIDTH,
+ FRAME_TITLE_HEIGHT );
+ if( !map_request ) {
+ values[ 0 ] = XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_COLOR_MAP_CHANGE;
+ xcb_change_window_attributes( c, w, XCB_CW_EVENT_MASK, values );
+
+ xcb_ungrab_server( c );
+ }
+
+ if( geom->border_width ) {
+ values[ 0 ] = 0;
+ xcb_configure_window( c, w, XCB_CONFIG_WINDOW_BORDER_WIDTH, values );
+ }
+
+#if USE_SHAPE
+ if( have_extension[ EXT_SHAPE ] && shape && shape->bounding_shaped )
+ match_managed_shape( window, TRUE );
+#endif
+
+ set_managed_state( window, map_request &&
+ ( window->u.managed.hints & HINT_ICONIC ) ?
+ STATE_ICONIC : STATE_NORMAL );
+
+ if( window->u.managed.state == STATE_NORMAL ) {
+ if( map_request )
+ xcb_map_window( c, w );
+
+ xcb_map_window( c, frame->w );
+ }
+
+ /* An ugly hack to get the button cursor properly set now that
+ the button has been created. */
+ managed_property_change( window, PROP_WM_PROTOCOLS,
+ props[ PROP_WM_PROTOCOLS ] );
+}
+
+struct new_window {
+ struct gwm_window *window; /* type WINDOW_INCOMPLETE */
+ int map_request;
+ xcb_get_geometry_cookie_t geometry_cookie;
+#if USE_SHAPE
+ xcb_shape_query_extents_cookie_t shape_cookie;
+#endif
+ xcb_get_property_cookie_t prop_cookies[ NUM_PROPS ];
+};
+
+static void handle_manage_window( unsigned int sequence, void *reply,
+ xcb_generic_error_t *error,
+ union callback_param cp ) {
+
+ struct new_window *nw = cp.p;
+ int i;
+ xcb_get_window_attributes_reply_t *attr = reply;
+ xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(
+ c, nw->geometry_cookie, NULL );
+#if USE_SHAPE
+ xcb_shape_query_extents_reply_t *shape = have_extension[ EXT_SHAPE ] ?
+ xcb_shape_query_extents_reply( c, nw->shape_cookie, NULL ) : NULL;
+#endif
+ xcb_get_property_reply_t *props[ NUM_PROPS ];
+ int have_all_props;
+
+ for( i = 0, have_all_props = TRUE; i < NUM_PROPS; i++ )
+ if( !( props[ i ] = xcb_get_property_reply( c, nw->prop_cookies[ i ],
+ NULL ) ) )
+ have_all_props = FALSE;
+
+ if( error ) {
+ show_error( error );
+
+ free( error );
+ }
+
+ if( attr && geom && have_all_props &&
+ attr->_class == XCB_WINDOW_CLASS_INPUT_OUTPUT &&
+ ( nw->map_request || ( attr->map_state == XCB_MAP_STATE_VIEWABLE &&
+ !attr->override_redirect ) ) )
+ start_managing_window( nw->window, attr, geom,
+#if USE_SHAPE
+ shape,
+#endif
+ props, nw->map_request );
+ else {
+ stop_listening( nw->window );
+ forget_window( nw->window );
+ }
+
+ if( attr )
+ free( attr );
+
+ if( geom )
+ free( geom );
+
+#if USE_SHAPE
+ if( shape )
+ free( shape );
+#endif
+
+ for( i = 0; i < NUM_PROPS; i++ )
+ if( props[ i ] )
+ free( props[ i ] );
+
+ free( nw );
+}
+
+extern void manage_window( xcb_window_t w, int map_request ) {
+
+ struct new_window *nw = xmalloc( sizeof *nw );
+ uint32_t values[ 2 ];
+ int i;
+ union callback_param cp;
+ unsigned int seq;
+
+ if( !( nw->window = add_window( w ) ) ) {
+ /* The window already exists. This can happen if a client attempts
+ more than one MapWindow before we've handle the first one, and
+ so we receive multiple MapRequests. */
+ free( nw );
+ return;
+ }
+
+ /* Our request for notification of events concerning the client
+ window must be atomic with our query of its state (otherwise
+ there will exist some interval where we will be unaware of updates,
+ or receive notifications for updates which were already present in
+ the originally queried state). Unfortunately this means that we're
+ forced to receive events before we even know whether we're interested
+ in managing the window. If we subsequently discover that we
+ didn't want the events after all, we will call stop_listening()
+ (below), and any corresponding events for the unknown window
+ will be silently ignored. */
+ xcb_grab_server( c );
+
+ nw->map_request = map_request;
+ nw->geometry_cookie = xcb_get_geometry( c, w );
+#if USE_SHAPE
+ if( have_extension[ EXT_SHAPE ] )
+ nw->shape_cookie = xcb_shape_query_extents( c, w );
+#endif
+ for( i = 0; i < NUM_PROPS; i++ )
+ nw->prop_cookies[ i ] = xcb_get_property( c, FALSE, w, prop_atoms[ i ],
+ prop_types[ i ], 0,
+ PROP_SIZE );
+
+ seq = xcb_get_window_attributes( c, w ).sequence;
+
+ values[ 0 ] = XCB_GRAVITY_NORTH_WEST;
+ values[ 1 ] = XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_COLOR_MAP_CHANGE;
+ xcb_change_window_attributes( c, w, XCB_CW_WIN_GRAVITY | XCB_CW_EVENT_MASK,
+ values );
+
+#if USE_SHAPE
+ if( have_extension[ EXT_SHAPE ] )
+ xcb_shape_select_input( c, w, TRUE );
+#endif
+
+ xcb_ungrab_server( c );
+
+ cp.p = nw;
+ handle_async_reply( seq, handle_manage_window, cp );
+
+ nw->window->type = WINDOW_INCOMPLETE;
+ nw->window->u.incomplete.sequence = seq;
+}
+
+extern void unmanage_window( struct gwm_window *window ) {
+
+ xcb_window_t w = window->w;
+ struct gwm_window *frame = window->u.managed.frame;
+ uint32_t border_width = window->u.managed.border_width;
+ int x, y;
+
+ if( focus_frame == frame )
+ focus_frame = NULL;
+
+ stop_listening( window );
+
+ /* Ignore Window errors on the child, because it might be destroyed
+ before we are notified. */
+
+ handle_error_reply( xcb_change_save_set_checked(
+ c, XCB_SET_MODE_DELETE, w ), ERR_MASK_WINDOW );
+
+ /* Reparent the window back to the root; delete its WM_STATE
+ property (see ICCCM 4.1.4); and map it if it was iconic. */
+ translate_frame_to_child( &x, &y, frame->u.frame.x, frame->u.frame.y,
+ border_width, window->u.managed.win_gravity );
+ handle_error_reply( xcb_reparent_window_checked(
+ c, w, screens[ window->screen ]->root,
+ x, y ), ERR_MASK_WINDOW );
+
+ handle_error_reply( xcb_delete_property_checked(
+ c, w, atoms[ ATOM_WM_STATE ] ), ERR_MASK_WINDOW );
+
+ handle_error_reply( xcb_configure_window_checked(
+ c, w, XCB_CONFIG_WINDOW_BORDER_WIDTH,
+ &border_width ), ERR_MASK_WINDOW );
+
+ if( window->u.managed.state == STATE_ICONIC )
+ handle_error_reply( xcb_map_window_checked( c, w ), ERR_MASK_WINDOW );
+
+ xcb_destroy_window( c, frame->w );
+
+ if( window->u.managed.name )
+ free( window->u.managed.name );
+
+ forget_window( window );
+ forget_window( frame->u.frame.button );
+ forget_window( frame );
+}
+
+extern void iconic_to_normal( struct gwm_window *window ) {
+
+ assert( window->type == WINDOW_MANAGED );
+
+ set_managed_state( window, STATE_NORMAL );
+ xcb_map_window( c, window->w );
+ xcb_map_window( c, window->u.managed.frame->w );
+}
+
+extern void deactivate_focus_frame( void ) {
+
+ uint32_t n;
+
+ if( !focus_frame )
+ return;
+
+ n = gwm_screens[ focus_frame->screen ].pixels[ COL_FRAME_INACTIVE ];
+ xcb_change_window_attributes( c, focus_frame->w, XCB_CW_BACK_PIXEL, &n );
+
+ queue_window_update( focus_frame, 0, 0, focus_frame->u.frame.width,
+ focus_frame->u.frame.height, FALSE );
+}
+
+extern void generic_expose( struct gwm_window *window,
+ xcb_expose_event_t *ev ) {
+
+ queue_window_update( window, ev->x, ev->y, ev->width, ev->height, TRUE );
+}
+
+xcb_window_t passive_grab; /* the window which has the pointer automatically
+ grabbed via a button press, or XCB_NONE */
+
+static void handle_fake_change_property( unsigned int sequence, void *reply,
+ xcb_generic_error_t *error,
+ union callback_param cp ) {
+
+ xcb_selection_notify_event_t *p = cp.p;
+
+ assert( !reply );
+
+ if( error ) {
+ free( error );
+
+ p->property = XCB_NONE;
+ }
+
+ handle_error_reply( xcb_send_event_checked( c, FALSE, p->requestor, 0,
+ (char *) p ), ERR_MASK_WINDOW );
+
+ free( p );
+}
+
+static void fake_selection_request( struct gwm_window *window,
+ xcb_selection_request_event_t *ev ) {
+
+ if( ev->time != XCB_CURRENT_TIME && ev->time < window->u.fake.timestamp &&
+ ev->target == atoms[ ATOM_VERSION ] ) {
+ /* ICCCM version identification: ICCCM 2.0, section 4.3. */
+ static const uint32_t values[ 2 ] = { 2, 0 };
+ struct xcb_selection_notify_event_t *p = xmalloc( sizeof *p );
+ union callback_param cp;
+
+ p->requestor = ev->requestor;
+ p->selection = ev->selection;
+ p->target = ev->target;
+ p->property = ev->property;
+ p->time = ev->time;
+
+ handle_async_reply( xcb_change_property_checked(
+ c, XCB_PROP_MODE_REPLACE, ev->requestor,
+ atoms[ ATOM_VERSION ], INTEGER, 32, 2,
+ values ).sequence, handle_fake_change_property,
+ cp );
+ } else {
+ struct xcb_selection_notify_event_t not;
+
+ not.requestor = ev->requestor;
+ not.selection = ev->selection;
+ not.target = ev->target;
+ not.property = XCB_NONE;
+ not.time = ev->time;
+
+ handle_error_reply( xcb_send_event_checked( c, FALSE, not.requestor,
+ 0, (char *) &not ),
+ ERR_MASK_WINDOW );
+ }
+}
+
+static event_handler fake_handlers[] = {
+ NULL, /* Error */
+ NULL, /* Reply */
+ NULL, /* KeyPress */
+ NULL, /* KeyRelease */
+ NULL, /* ButtonPress */
+ NULL, /* ButtonRelease */
+ NULL, /* MotionNotify */
+ NULL, /* EnterNotify */
+ NULL, /* LeaveNotify */
+ NULL, /* FocusIn */
+ NULL, /* FocusOut */
+ NULL, /* KeymapNotify */
+ NULL, /* Expose */
+ NULL, /* GraphicsExpose */
+ NULL, /* NoExposure */
+ NULL, /* VisibilityNotify */
+ NULL, /* CreateNotify */
+ NULL, /* DestroyNotify */
+ NULL, /* UnmapNotify */
+ NULL, /* MapNotify */
+ NULL, /* MapRequest */
+ NULL, /* ReparentNotify */
+ NULL, /* ConfigureNotify */
+ NULL, /* ConfigureRequest */
+ NULL, /* GravityNotify */
+ NULL, /* ResizeRequest */
+ NULL, /* CirculateNotify */
+ NULL, /* CirculateRequest */
+ NULL, /* PropertyNotify */
+ NULL, /* SelectionClear */
+ (event_handler) fake_selection_request, /* SelectionRequest */
+ NULL, /* SelectionNotify */
+ NULL, /* ColormapNotify */
+ NULL, /* ClientMessage */
+ NULL, /* MappingNotify */
+ NULL, /* (synthetic) */
+ NULL /* ShapeNotify */
+};
+
+static event_handler feedback_handlers[] = {
+ NULL, /* Error */
+ NULL, /* Reply */
+ NULL, /* KeyPress */
+ NULL, /* KeyRelease */
+ NULL, /* ButtonPress */
+ NULL, /* ButtonRelease */
+ NULL, /* MotionNotify */
+ NULL, /* EnterNotify */
+ NULL, /* LeaveNotify */
+ NULL, /* FocusIn */
+ NULL, /* FocusOut */
+ NULL, /* KeymapNotify */
+ (event_handler) generic_expose,
+ NULL, /* GraphicsExpose */
+ NULL, /* NoExposure */
+ NULL, /* VisibilityNotify */
+ NULL, /* CreateNotify */
+ NULL, /* DestroyNotify */
+ NULL, /* UnmapNotify */
+ NULL, /* MapNotify */
+ NULL, /* MapRequest */
+ NULL, /* ReparentNotify */
+ NULL, /* ConfigureNotify */
+ NULL, /* ConfigureRequest */
+ NULL, /* GravityNotify */
+ NULL, /* ResizeRequest */
+ NULL, /* CirculateNotify */
+ NULL, /* CirculateRequest */
+ NULL, /* PropertyNotify */
+ NULL, /* SelectionClear */
+ NULL, /* SelectionRequest */
+ NULL, /* SelectionNotify */
+ NULL, /* ColormapNotify */
+ NULL, /* ClientMessage */
+ NULL, /* MappingNotify */
+ NULL /* (synthetic) */
+};
+
+static const event_handler *handlers[] = {
+ root_handlers, managed_handlers, frame_handlers, button_handlers,
+ fake_handlers, feedback_handlers, NULL
+};
+
+static void setup_display( void ) {
+
+ int i;
+ xcb_intern_atom_cookie_t atom_cookies[ NUM_ATOMS ];
+ const xcb_setup_t *setup;
+ xcb_screen_iterator_t iter;
+ xcb_intern_atom_cookie_t *screen_atom_cookies;
+ uint32_t n;
+ xcb_get_selection_owner_cookie_t *screen_owner_cookies;
+ xcb_window_t *screen_owners;
+ xcb_client_message_event_t msg;
+ xcb_void_cookie_t *cookies;
+ xcb_query_tree_cookie_t *tree_cookies;
+
+ table_init( &windows );
+ table_init( &update_windows );
+
+ c = xcb_connect( NULL, NULL );
+
+ if( xcb_connection_has_error( c ) )
+ fatal( "could not connect to server" );
+
+ for( i = 0; i < NUM_EXTENSIONS; i++ )
+ xcb_prefetch_extension_data( c, (xcb_extension_t *) extensions[ i ] );
+
+ for( i = 0; i < NUM_ATOMS; i++ )
+ atom_cookies[ i ] = xcb_intern_atom( c, 0, strlen( atom_names[ i ] ),
+ atom_names[ i ] );
+
+ setup = xcb_get_setup( c );
+
+ iter = xcb_setup_roots_iterator( setup );
+ num_screens = iter.rem;
+ screens = xmalloc( num_screens * sizeof *screens );
+ gwm_screens = xmalloc( num_screens * sizeof *gwm_screens );
+ screen_atom_cookies = alloca( num_screens * sizeof *screen_atom_cookies );
+ screen_owners = alloca( num_screens * sizeof *screen_owners );
+ screen_owner_cookies = alloca( num_screens * sizeof *screen_owner_cookies );
+ cookies = alloca( num_screens * sizeof *cookies );
+ tree_cookies = alloca( num_screens * sizeof *tree_cookies );
+
+ for( i = 0; iter.rem; i++, xcb_screen_next( &iter ) ) {
+ static char wm_atom_str[ 16 ] = "WM_S";
+ xcb_depth_iterator_t depth_iter;
+
+ screens[ i ] = iter.data;
+ screen_atom_cookies[ i ] =
+ xcb_intern_atom( c, 0, 4 + sprintf( wm_atom_str + 4, "%d", i ),
+ wm_atom_str );
+
+ for( depth_iter = xcb_screen_allowed_depths_iterator( screens[ i ] );
+ depth_iter.rem; xcb_depth_next( &depth_iter ) ) {
+ xcb_visualtype_iterator_t visual_iter;
+
+ for( visual_iter = xcb_depth_visuals_iterator( depth_iter.data );
+ visual_iter.rem; xcb_visualtype_next( &visual_iter ) ) {
+ xcb_visualtype_t *visual = visual_iter.data;
+
+ if( screens[ i ]->root_visual == visual->visual_id ) {
+ gwm_screens[ i ].root_visual = visual;
+ goto visual_found;
+ }
+ }
+ }
+ fatal( "Root visual for screen %d (0x%X) not found", i,
+ screens[ i ]->root_visual );
+ visual_found:
+ ;
+ }
+
+ fake_window = add_window( xcb_generate_id( c ) );
+ fake_window->screen = 0;
+ fake_window->type = WINDOW_FAKE;
+
+ n = XCB_EVENT_MASK_PROPERTY_CHANGE;
+ xcb_create_window( c, 0, fake_window->w, screens[ 0 ]->root, 0, 0, 1, 1, 0,
+ XCB_WINDOW_CLASS_INPUT_ONLY, screens[ 0 ]->root_visual,
+ XCB_CW_EVENT_MASK, &n );
+
+ /* Obtain a timestamp we can use for our selection acquiry time
+ (ICCCM 2.0, section 2.1). */
+ xcb_change_property( c, XCB_PROP_MODE_APPEND, fake_window->w, WM_HINTS,
+ WM_HINTS, 32, 0, NULL );
+
+ xcb_flush( c ); /* BLOCK */
+
+ for( i = 0; i < NUM_EXTENSIONS; i++ ) {
+ const xcb_query_extension_reply_t *r;
+
+ if( ( have_extension[ i ] =
+ ( r = xcb_get_extension_data(
+ c, (xcb_extension_t *) extensions[ i ] ) ) && r->present ) ) {
+ extension_event[ i ] = r->first_event;
+ extension_error[ i ] = r->first_error;
+ }
+ }
+
+ decorate_core_init();
+ update_window = core_update_window;
+
+ for( i = 0; i < NUM_ATOMS; i++ ) {
+ xcb_intern_atom_reply_t *r =
+ xcb_intern_atom_reply( c, atom_cookies[ i ], NULL );
+
+ atoms[ i ] = r->atom;
+ free( r );
+ }
+
+ prop_atoms[ PROP_WM_COLORMAP_WINDOWS ] = atoms[ ATOM_WM_COLORMAP_WINDOWS ];
+ prop_atoms[ PROP_WM_HINTS ] = WM_HINTS;
+ prop_atoms[ PROP_WM_NAME ] = WM_NAME;
+ prop_atoms[ PROP_WM_NORMAL_HINTS ] = WM_NORMAL_HINTS;
+ prop_atoms[ PROP_WM_PROTOCOLS ] = atoms[ ATOM_WM_PROTOCOLS ];
+
+ prop_types[ PROP_WM_COLORMAP_WINDOWS ] = WINDOW;
+ prop_types[ PROP_WM_HINTS ] = WM_HINTS;
+ prop_types[ PROP_WM_NAME ] = XCB_GET_PROPERTY_TYPE_ANY;
+ prop_types[ PROP_WM_NORMAL_HINTS ] = WM_SIZE_HINTS;
+ prop_types[ PROP_WM_PROTOCOLS ] = ATOM;
+
+ for( i = 0; i < num_screens; i++ ) {
+ xcb_intern_atom_reply_t *r =
+ xcb_intern_atom_reply( c, screen_atom_cookies[ i ], NULL );
+
+ gwm_screens[ i ].wm_atom = r->atom;
+ free( r );
+ }
+
+ /* Listen for a selection timestamp. */
+ for(;;) {
+ /* BLOCK */
+ xcb_generic_event_t *ev = wait_for_event();
+
+ if( !ev )
+ fatal( "did not receive property notification" );
+
+ if( ev->response_type & SEND_EVENT_MASK ) {
+ free( ev );
+
+ continue; /* ignore synthetic events */
+ } else if( ev->response_type == XCB_PROPERTY_NOTIFY ) {
+ xcb_property_notify_event_t *not =
+ (xcb_property_notify_event_t *) ev;
+
+ if( not->window != fake_window->w ||
+ not->atom != WM_HINTS ||
+ not->state != XCB_PROPERTY_NEW_VALUE ) {
+ warning( "unexpected property notification" );
+
+ continue;
+ }
+
+ fake_window->u.fake.timestamp = not->time;
+
+ free( ev );
+
+ break;
+ } else if( ev->response_type != XCB_MAPPING_NOTIFY ) {
+ /* Ignore MappingNotify -- we'll query the mappings later. */
+ warning( "unexpected event while waiting for property "
+ "notification" );
+
+#if DEBUG
+ show_event( ev );
+#endif
+
+ free( ev );
+ }
+ }
+
+ n = 0;
+ xcb_change_window_attributes( c, fake_window->w, XCB_CW_EVENT_MASK, &n );
+
+ do {
+ int existing_manager;
+ int managers_changed;
+ sigset_t sigs, oldsigs;
+
+ for( i = 0; i < num_screens; i++ )
+ screen_owner_cookies[ i ] =
+ xcb_get_selection_owner( c, gwm_screens[ i ].wm_atom );
+
+ xcb_flush( c ); /* BLOCK */
+
+ existing_manager = 0;
+ for( i = 0; i < num_screens; i++ ) {
+ xcb_get_selection_owner_reply_t *r =
+ xcb_get_selection_owner_reply( c, screen_owner_cookies[ i ],
+ NULL );
+
+ if( ( screen_owners[ i ] = r->owner ) != XCB_NONE ) {
+ if( !flag_replace )
+ printf( "Screen %d is already managed.\n", i );
+
+ existing_manager++;
+ }
+
+ free( r );
+ }
+
+ if( existing_manager && !flag_replace ) {
+ printf( "Use \"%s --replace\" to replace any existing "
+ "window manager(s).\n", argv0 );
+
+ exit( 1 );
+ }
+
+ if( existing_manager ) {
+ uint32_t n;
+
+ /* Wait for existing manager(s) to terminate (ICCCM 2.0,
+ section 2.8). */
+ n = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ for( i = 0; i < num_screens; i++ )
+ if( screen_owners[ i ] != XCB_NONE )
+ xcb_change_window_attributes( c, screen_owners[ i ],
+ XCB_CW_EVENT_MASK, &n );
+
+ for( i = 0; i < num_screens; i++ )
+ screen_owner_cookies[ i ] =
+ xcb_get_selection_owner( c, gwm_screens[ i ].wm_atom );
+
+ xcb_flush( c ); /* BLOCK */
+
+ managers_changed = FALSE;
+ for( i = 0; i < num_screens; i++ ) {
+ xcb_get_selection_owner_reply_t *r =
+ xcb_get_selection_owner_reply( c, screen_owner_cookies[ i ],
+ NULL );
+
+ if( r->owner != screen_owners[ i ] )
+ managers_changed = TRUE;
+
+ free( r );
+ }
+
+ if( managers_changed ) {
+ /* Argh. A window manager changed during all our bookkeeping.
+ Undo our notifications, and start again. */
+ n = 0;
+ for( i = 0; i < num_screens; i++ )
+ if( screen_owners[ i ] != XCB_NONE )
+ xcb_change_window_attributes( c, screen_owners[ i ],
+ XCB_CW_EVENT_MASK, &n );
+
+ continue;
+ }
+ }
+
+ /* Acquire ownership of WM_Sn selections (ICCCM 2.0, section 4.3). */
+ for( i = 0; i < num_screens; i++ ) {
+ xcb_set_selection_owner( c, fake_window->w,
+ gwm_screens[ i ].wm_atom,
+ fake_window->u.fake.timestamp );
+ screen_owner_cookies[ i ] =
+ xcb_get_selection_owner( c, gwm_screens[ i ].wm_atom );
+ }
+
+ xcb_flush( c ); /* BLOCK */
+ sync_with_callback( screen_owner_cookies[ num_screens - 1 ].sequence );
+
+ for( i = 0; i < num_screens; i++ ) {
+ xcb_get_selection_owner_reply_t *r =
+ xcb_get_selection_owner_reply( c, screen_owner_cookies[ i ],
+ NULL );
+
+ if( r->owner != fake_window->w )
+ fatal( "did not acquire window manager selection" );
+
+ free( r );
+ }
+
+ alarm( 3 );
+
+ /* Listen for DestroyNotify on any selection holders. */
+ while( existing_manager ) {
+ static int killed_existing_managers;
+
+ xcb_generic_event_t *ev = wait_for_event(); /* BLOCK */
+
+ if( !ev ) {
+ if( flag_force && signal_caught == SIGALRM &&
+ !killed_existing_managers ) {
+ signal_caught = 0;
+
+ for( i = 0; i < num_screens; i++ )
+ if( screen_owners[ i ] )
+ xcb_kill_client( c, screen_owners[ i ] );
+
+ killed_existing_managers = TRUE;
+
+ alarm( 3 );
+
+ continue;
+ }
+
+ fatal( "did not receive destroy notification" );
+ }
+
+ if( ev->response_type & SEND_EVENT_MASK ) {
+ free( ev );
+
+ continue; /* ignore synthetic events */
+ } else if( ev->response_type == XCB_DESTROY_NOTIFY ) {
+ xcb_destroy_notify_event_t *not =
+ (xcb_destroy_notify_event_t *) ev;
+
+ for( i = 0; i < num_screens; i++ )
+ if( not->window == screen_owners[ i ] ) {
+ screen_owners[ i ] = XCB_NONE;
+ existing_manager--;
+ }
+
+ free( ev );
+ } else if( ev->response_type == XCB_SELECTION_CLEAR )
+ /* We lost a window manager selection before we even
+ started. Just shut down quietly. */
+ exit( 0 );
+ else if( ev->response_type == XCB_MAPPING_NOTIFY )
+ /* Ignore MappingNotify. */
+ free( ev );
+ else if( !ev->response_type ) {
+ /* Error. */
+ xcb_generic_error_t *error = (xcb_generic_error_t *) ev;
+
+ /* Expect Window and Value errors, because old window managers
+ might die before we sent a corresponding request. */
+ if( error->error_code != XCB_WINDOW &&
+ error->error_code != XCB_VALUE )
+ show_error( error );
+
+ free( ev );
+ } else {
+ warning( "unexpected event while waiting for destroy "
+ "notification" );
+
+#if DEBUG
+ show_event( ev );
+#endif
+
+ free( ev );
+ }
+ }
+
+ sigfillset( &sigs );
+ sigprocmask( SIG_BLOCK, &sigs, &oldsigs );
+
+ alarm( 0 );
+ if( signal_caught == SIGALRM )
+ signal_caught = 0;
+
+ sigprocmask( SIG_SETMASK, &oldsigs, NULL );
+ } while( 0 );
+
+ msg.response_type = XCB_CLIENT_MESSAGE;
+ msg.format = 32;
+ msg.sequence = 0;
+ msg.type = atoms[ ATOM_MANAGER ];
+ msg.data.data32[ 0 ] = fake_window->u.fake.timestamp;
+ /* Data32[ 1 ] varies; will be completed later. */
+ msg.data.data32[ 2 ] = fake_window->w;
+ msg.data.data32[ 3 ] = 0;
+ msg.data.data32[ 4 ] = 0;
+
+ n = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
+ XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
+
+ for( i = 0; i < num_screens; i++ ) {
+ /* Send StructureNotify ClientMessage (ICCCM 2.0, section 2.8). */
+ msg.window = screens[ i ]->root;
+ msg.data.data32[ 1 ] = gwm_screens[ i ].wm_atom;
+ xcb_send_event( c, FALSE, screens[ i ]->root,
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *) &msg );
+
+ cookies[ i ] =
+ xcb_change_window_attributes_checked( c, screens[ i ]->root,
+ XCB_CW_EVENT_MASK, &n );
+
+ tree_cookies[ i ] = xcb_query_tree( c, screens[ i ]->root );
+ }
+
+ xcb_set_input_focus( c, XCB_INPUT_FOCUS_NONE, XCB_INPUT_FOCUS_POINTER_ROOT,
+ fake_window->u.fake.timestamp );
+
+ get_keyboard_mapping( setup->min_keycode,
+ setup->max_keycode - setup->min_keycode + 1 );
+ get_modifier_mapping();
+
+ xcb_flush( c ); /* BLOCK */
+ sync_with_callback( cookies[ num_screens - 1 ].sequence );
+
+ for( i = 0; i < num_screens; i++ ) {
+ xcb_generic_error_t *error = xcb_request_check( c, cookies[ i ] );
+ xcb_query_tree_reply_t *r;
+ struct gwm_window *window;
+ xcb_window_t *children;
+ int j;
+
+ if( error )
+ fatal( "could not acquire SubstructureRedirect" );
+
+ window = add_window( screens[ i ]->root );
+ window->screen = i;
+ window->type = WINDOW_ROOT;
+
+ gwm_screens[ i ].cmap = XCB_NONE;
+ gwm_screens[ i ].cmap_time = fake_window->u.fake.timestamp;
+
+ r = xcb_query_tree_reply( c, tree_cookies[ i ], NULL );
+ children = xcb_query_tree_children( r );
+ for( j = 0; j < r->children_len; j++ )
+ manage_window( children[ j ], FALSE );
+
+ free( r );
+ }
+}
+
+static void handle_mapping_notify( xcb_mapping_notify_event_t *ev ) {
+
+ switch( ev->request ) {
+ case XCB_MAPPING_MODIFIER:
+ get_modifier_mapping();
+ break;
+
+ case XCB_MAPPING_KEYBOARD:
+ get_keyboard_mapping( ev->first_keycode, ev->count );
+ break;
+
+ case XCB_MAPPING_POINTER:
+ break;
+ }
+}
+
+static void handle_client_message( xcb_client_message_event_t *ev ) {
+
+ if( ev->type == atoms[ ATOM_WM_CHANGE_STATE ] ) {
+ struct gwm_window *window;
+
+ if( ( window = lookup_window( ev->window ) ) &&
+ window->type == WINDOW_MANAGED &&
+ window->u.managed.state == STATE_NORMAL &&
+ ev->data.data32[ 0 ] == STATE_ICONIC ) {
+ uint32_t n;
+
+ set_managed_state( window, STATE_ICONIC );
+ /* We need to unmap our child without invoking the normal
+ UnmapNotify handler response. Unfortunately it's very
+ difficult to communicate to the handler the distinction
+ between the resultant UnmapNotify from an unmap here
+ and an unrelated event. Checking the request sequence
+ is not robust, because asynchronous events might not be
+ assigned unique sequence numbers. Instead, we grab
+ the server and temporarily change our event mask, which
+ seems a heavyweight approach but does guarantee that
+ only this event will be ignored. */
+ xcb_grab_server( c );
+
+ n = XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_COLOR_MAP_CHANGE;
+ xcb_change_window_attributes( c, window->w, XCB_CW_EVENT_MASK, &n );
+
+ xcb_unmap_window( c, window->w );
+
+ n = XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_PROPERTY_CHANGE |
+ XCB_EVENT_MASK_COLOR_MAP_CHANGE;
+ xcb_change_window_attributes( c, window->w, XCB_CW_EVENT_MASK, &n );
+
+ xcb_ungrab_server( c );
+
+ xcb_unmap_window( c, window->u.managed.frame->w );
+ }
+ } else if( ev->type == atoms[ ATOM_WM_COLORMAP_NOTIFY ] ) {
+ struct gwm_window *window;
+
+ if( ( window = lookup_window( ev->window ) ) &&
+ window->type == WINDOW_ROOT &&
+ !ev->data.data32[ 1 ] )
+ /* Ignore the case where the client is starting installation:
+ if it is well-behaved, it will do so only when the pointer
+ is grabbed (in which case we won't do anything to conflict
+ with it); if it is not well-behaved, it's not worth trying
+ to accomodate it. */
+ install_window_colormap( window->screen, focus_frame ?
+ focus_frame->u.frame.child : NULL,
+ ev->data.data32[ 0 ] );
+ }
+}
+
+static void handle_events( void ) {
+
+ xcb_generic_event_t *ev;
+
+ while( ( ev = wait_for_event() ) ) {
+ int event_type = ev->response_type;
+ int event_base_type = event_type & 0x7F;
+ xcb_window_t w;
+
+ /* Determine event window and time. */
+ switch( event_base_type ) {
+ case 0: /* Error */
+ case 1: /* Reply */
+ case XCB_KEYMAP_NOTIFY:
+ case XCB_CLIENT_MESSAGE:
+ case XCB_MAPPING_NOTIFY:
+ w = XCB_NONE;
+ break;
+
+ case XCB_KEY_PRESS:
+ w = ( (xcb_key_press_event_t *) ev )->event;
+ latest_timestamp = ( (xcb_key_press_event_t *) ev )->time;
+ break;
+
+ case XCB_KEY_RELEASE:
+ w = ( (xcb_key_release_event_t *) ev )->event;
+ latest_timestamp = ( (xcb_key_release_event_t *) ev )->time;
+ break;
+
+ case XCB_BUTTON_PRESS:
+ w = ( (xcb_button_press_event_t *) ev )->event;
+ latest_timestamp = ( (xcb_button_press_event_t *) ev )->time;
+ break;
+
+ case XCB_BUTTON_RELEASE:
+ w = ( (xcb_button_release_event_t *) ev )->event;
+ latest_timestamp = ( (xcb_button_release_event_t *) ev )->time;
+ break;
+
+ case XCB_MOTION_NOTIFY:
+ w = ( (xcb_motion_notify_event_t *) ev )->event;
+ latest_timestamp = ( (xcb_motion_notify_event_t *) ev )->time;
+ break;
+
+ case XCB_ENTER_NOTIFY:
+ case XCB_LEAVE_NOTIFY:
+ w = ( (xcb_enter_notify_event_t *) ev )->event;
+ latest_timestamp = ( (xcb_enter_notify_event_t *) ev )->time;
+ break;
+
+ case XCB_FOCUS_IN:
+ case XCB_FOCUS_OUT:
+ w = ( (xcb_focus_in_event_t *) ev )->event;
+ break;
+
+ case XCB_EXPOSE:
+ w = ( (xcb_expose_event_t *) ev )->window;
+ break;
+
+ case XCB_GRAPHICS_EXPOSURE:
+ w = ( (xcb_graphics_exposure_event_t *) ev )->drawable;
+ break;
+
+ case XCB_NO_EXPOSURE:
+ w = ( (xcb_no_exposure_event_t *) ev )->drawable;
+ break;
+
+ case XCB_VISIBILITY_NOTIFY:
+ w = ( (xcb_visibility_notify_event_t *) ev )->window;
+ break;
+
+ case XCB_CREATE_NOTIFY:
+ w = ( (xcb_create_notify_event_t *) ev )->parent;
+ break;
+
+ case XCB_DESTROY_NOTIFY:
+ w = ( (xcb_destroy_notify_event_t *) ev )->event;
+ break;
+
+ case XCB_UNMAP_NOTIFY:
+ w = ( (xcb_unmap_notify_event_t *) ev )->event;
+ break;
+
+ case XCB_MAP_NOTIFY:
+ w = ( (xcb_map_notify_event_t *) ev )->event;
+ break;
+
+ case XCB_MAP_REQUEST:
+ w = ( (xcb_map_request_event_t *) ev )->parent;
+ break;
+
+ case XCB_REPARENT_NOTIFY:
+ w = ( (xcb_reparent_notify_event_t *) ev )->event;
+ break;
+
+ case XCB_CONFIGURE_NOTIFY:
+ w = ( (xcb_configure_notify_event_t *) ev )->event;
+ break;
+
+ case XCB_CONFIGURE_REQUEST:
+ w = ( (xcb_configure_request_event_t *) ev )->parent;
+ break;
+
+ case XCB_GRAVITY_NOTIFY:
+ w = ( (xcb_gravity_notify_event_t *) ev )->event;
+ break;
+
+ case XCB_RESIZE_REQUEST:
+ w = ( (xcb_resize_request_event_t *) ev )->window;
+ break;
+
+ case XCB_CIRCULATE_NOTIFY:
+ case XCB_CIRCULATE_REQUEST:
+ w = ( (xcb_circulate_notify_event_t *) ev )->event;
+ break;
+
+ case XCB_PROPERTY_NOTIFY:
+ w = ( (xcb_property_notify_event_t *) ev )->window;
+ latest_timestamp = ( (xcb_property_notify_event_t *) ev )->time;
+ break;
+
+ case XCB_SELECTION_CLEAR:
+ w = ( (xcb_selection_clear_event_t *) ev )->owner;
+ latest_timestamp = ( (xcb_selection_clear_event_t *) ev )->time;
+ break;
+
+ case XCB_SELECTION_REQUEST:
+ w = ( (xcb_selection_request_event_t *) ev )->owner;
+ if( ( (xcb_selection_request_event_t *) ev )->time )
+ latest_timestamp =
+ ( (xcb_selection_request_event_t *) ev )->time;
+ break;
+
+ case XCB_SELECTION_NOTIFY:
+ w = ( (xcb_selection_notify_event_t *) ev )->requestor;
+ if( ( (xcb_selection_notify_event_t *) ev )->time )
+ latest_timestamp =
+ ( (xcb_selection_notify_event_t *) ev )->time;
+ break;
+
+ case XCB_COLORMAP_NOTIFY:
+ w = ( (xcb_colormap_notify_event_t *) ev )->window;
+ break;
+
+ default:
+#if USE_SHAPE
+ if( event_base_type == extension_event[ EXT_SHAPE ] ) {
+ w = ( (xcb_shape_notify_event_t *) ev )->affected_window;
+ event_type = SHAPE_NOTIFY;
+ break;
+ }
+#endif
+ warning( "Unknown event" );
+ break;
+ }
+
+ if( ev->response_type & 0x80 )
+ event_type = SYNTHETIC_EVENT;
+
+ /* Global handling for events (before specific window handler). */
+ switch( event_type ) {
+ case 0: /* Error */
+ show_error( (xcb_generic_error_t *) ev );
+ break;
+
+ case 1: /* Reply */
+ warning( "handle_events: received reply message" );
+ break;
+
+ case XCB_BUTTON_PRESS:
+ if( initial_press( (xcb_button_press_event_t *) ev )
+ && !passive_grab )
+ passive_grab = w;
+ break;
+
+ case XCB_KEYMAP_NOTIFY:
+ break;
+
+ case XCB_SELECTION_CLEAR:
+ /* We've lost the manager selection (see ICCCM 2.0, section
+ 2.8); close down gracefully. */
+ free( ev );
+ return;
+
+ case XCB_MAPPING_NOTIFY:
+ handle_mapping_notify( (xcb_mapping_notify_event_t *) ev );
+ break;
+
+ case SYNTHETIC_EVENT:
+ if( ev->response_type == ( XCB_CLIENT_MESSAGE | SEND_EVENT_MASK ) )
+ handle_client_message( (xcb_client_message_event_t *) ev );
+ break;
+ }
+
+ if( w != XCB_NONE ) {
+ struct gwm_window *window;
+ void ( *handler )( struct gwm_window *, xcb_generic_event_t * );
+
+ if( ( window = lookup_window( w ) ) &&
+ handlers[ window->type ] &&
+ ( handler = handlers[ window->type ][ event_type ] ) )
+ handler( window, ev );
+ }
+
+ /* Global handling for events (after specific window handler). */
+ switch( event_type ) {
+ case XCB_BUTTON_RELEASE:
+ if( final_release( (xcb_button_release_event_t *) ev ) )
+ passive_grab = XCB_NONE;
+ break;
+ }
+
+ free( ev );
+ }
+}
+
+static void handle_intern_atom( unsigned int sequence, void *reply,
+ xcb_generic_error_t *error,
+ union callback_param p ) {
+
+ if( error )
+ free( error );
+
+ if( reply )
+ free( reply );
+}
+
+static void shutdown_display( void ) {
+
+ int i;
+ uint32_t n;
+ xcb_intern_atom_cookie_t cookie;
+ union callback_param cp;
+
+retry:
+ for( i = 0; i < windows.used; i++ )
+ if( windows.values[ i ]->type == WINDOW_MANAGED ) {
+ unmanage_window( windows.values[ i ] );
+
+ goto retry;
+ }
+
+ /* The ICCCM says that using CurrentTime for SetInputFocus is naughty
+ (section 4.2.7). But we don't have much alternative in this case:
+ if we're shutting down in response to a signal, for instance. */
+ xcb_set_input_focus( c, XCB_INPUT_FOCUS_NONE, XCB_INPUT_FOCUS_POINTER_ROOT,
+ XCB_CURRENT_TIME );
+
+ decorate_core_done();
+
+ n = 0;
+ for( i = 0; i < num_screens; i++ )
+ xcb_change_window_attributes( c, screens[ i ]->root,
+ XCB_CW_EVENT_MASK, &n );
+
+ /* A pointless request to generate a reply, so we'll know when we've
+ processed everything the server will send us. */
+ cookie = xcb_intern_atom( c, TRUE, 4, "ATOM" );
+ cp.l = 0;
+ handle_async_reply( cookie.sequence, handle_intern_atom, cp );
+
+ xcb_flush( c );
+
+ sync_with_callback( cookie.sequence );
+
+ xcb_disconnect( c );
+#if DEBUG
+ /* Be pedantic about deallocating memory, to assist any tools that are
+ looking for leaks. */
+ forget_window( fake_window );
+
+retry_root:
+ for( i = 0; i < windows.used; i++ )
+ if( windows.values[ i ]->type == WINDOW_ROOT ) {
+ forget_window( windows.values[ i ] );
+
+ goto retry_root;
+ }
+
+ /* All windows have now been deallocated -- release the tables, too. */
+ table_destroy( &windows );
+ table_destroy( &update_windows );
+
+ cleanup_keyboard();
+
+ free( screens );
+ free( gwm_screens );
+
+ cleanup_utf8();
+
+ muntrace();
+#endif
+}
+
+static void unknown( char *opt ) {
+
+ printf( "%s: unknown option %s\n"
+ "Usage: %s [OPTION]...\n"
+ "Try \"%s --help\" for more information.\n",
+ argv0, opt, argv0, argv0 );
+}
+
+extern int main( int argc, char *argv[] ) {
+
+ int i, j;
+ struct sigaction sa;
+ int which_signal;
+ static int flag_help, flag_version;
+ static const struct option {
+ const char *opt;
+ int *flag;
+ } options[] = {
+#if DEBUG
+ { "debug", &flag_debug },
+#endif
+ { "force", &flag_force },
+ { "help", &flag_help },
+ { "replace", &flag_replace },
+ { "version", &flag_version }
+ };
+#define NUM_OPTIONS ( sizeof options / sizeof *options )
+
+#if DEBUG && HAVE_MTRACE
+ mtrace();
+#endif
+
+ argv0 = argv[ 0 ];
+
+ for( i = 1; i < argc; i++ )
+ if( argv[ i ][ 0 ] == '-' ) {
+ if( argv[ i ][ 1 ] == '-' && argv[ i ][ 2 ] ) {
+ for( j = 0; j < NUM_OPTIONS; j++ )
+ if( !strncmp( argv[ i ] + 2, options[ j ].opt,
+ strlen( argv[ i ] + 2 ) ) ) {
+ *options[ j ].flag = 1;
+ break;
+ }
+
+ if( j == NUM_OPTIONS ) {
+ unknown( argv[ i ] );
+
+ return 1;
+ }
+ } else if( argv[ i ][ 1 ] != '-' ) {
+ for( j = 0; j < NUM_OPTIONS; j++ )
+ if( argv[ i ][ 1 ] == options[ j ].opt[ 0 ] &&
+ !argv[ i ][ 2 ] ) {
+ *options[ j ].flag = 1;
+ break;
+ }
+
+ if( j == NUM_OPTIONS ) {
+ unknown( argv[ i ] );
+
+ return 1;
+ }
+ }
+ } else {
+ unknown( argv[ i ] );
+
+ return 1;
+ }
+
+ if( flag_help ) {
+ printf( "Usage: %s [OPTION]...\n"
+ "Options:\n"
+#if DEBUG
+ " -d, --debug Show all events received\n"
+#endif
+ " -f, --force If replacing another window manager, "
+ "terminate it if necessary\n"
+ " -h, --help Display this information and exit\n"
+ " -r, --replace Take over window management if another "
+ "manager exists\n"
+ " -v, --version Display version information and exit\n"
+ "Please send bug reports to <" PACKAGE_BUGREPORT ">.\n",
+ argv0 );
+
+ return 0;
+ }
+
+ if( flag_version ) {
+ puts( PACKAGE_STRING "\nPlease send bug reports to <"
+ PACKAGE_BUGREPORT ">." );
+
+ return 0;
+ }
+
+ sa.sa_handler = catch_signal;
+ sigemptyset( &sa.sa_mask );
+ sa.sa_flags = 0;
+ sigaction( SIGHUP, &sa, NULL );
+ sigaction( SIGINT, &sa, NULL );
+ sigaction( SIGTERM, &sa, NULL );
+
+ sa.sa_handler = alarm_signal;
+ sigaction( SIGALRM, &sa, NULL );
+
+ sa.sa_handler = child_signal;
+ sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
+ sigaction( SIGCHLD, &sa, NULL );
+
+ setup_display();
+
+ handle_events();
+ which_signal = signal_caught;
+ signal_caught = 0; /* acknowledge receipt, so we get normal event
+ processing during shutdown */
+
+ if( xcb_connection_has_error( c ) )
+ /* Don't bother attempting orderly shutdown. */
+ fatal( "server connection error" );
+
+ shutdown_display();
+
+ if( which_signal ) {
+ raise( which_signal );
+
+ return 1;
+ }
+
+ return 0;
+}