diff options
author | Gary Wong <gtw@gnu.org> | 2009-08-22 22:31:14 -0600 |
---|---|---|
committer | Gary Wong <gtw@gnu.org> | 2009-08-22 22:31:14 -0600 |
commit | f56f44b401baa435693efe497b0cda8749a279fe (patch) | |
tree | a59f7b711c8a5bdbe19f034d49123d7c0739100d /gwm.c |
Initial commit (gwm-basic 1.0).
Diffstat (limited to 'gwm.c')
-rw-r--r-- | gwm.c | 2601 |
1 files changed, 2601 insertions, 0 deletions
@@ -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 *) ¬ ), + 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; +} |