/* * gwm.c * * Part of gwm, the Gratuitous Window Manager, * by Gary Wong, . * * 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 . * * $Id$ */ #include #include #include #include #if HAVE_MCHECK_H #include #endif #if HAVE_SYS_MMAN_H #include #endif #if HAVE_POLL_H #include #endif #include #include #if HAVE_STDINT_H #include #endif #include #include #include #if HAVE_UNISTD_H #include #endif #include #if USE_COMPOSITE #include #endif #if USE_DAMAGE #include #endif #if USE_RANDR #include #endif #if USE_RENDER #include #endif #if USE_SHAPE #include #endif #include #include #if USE_XFIXES #include #endif #include "gwm.h" #include "button.h" #include "decorate-core.h" #if USE_RENDER #include "decorate-render.h" #endif #include "frame.h" #include "keyboard.h" #include "managed.h" #include "menu.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 *const atom_names[ NUM_ATOMS ] = { "COMPOUND_TEXT", "MANAGER", "_MOTIF_WM_HINTS", "_NET_CURRENT_DESKTOP", "_NET_DESKTOP_GEOMETRY", "_NET_DESKTOP_VIEWPORT", "_NET_FRAME_EXTENTS", "_NET_NUMBER_OF_DESKTOPS", "_NET_SUPPORTED", "_NET_SUPPORTING_WM_CHECK", "_NET_WM_ICON", "_NET_WM_NAME", "_NET_WORKAREA", "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 ]; 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; const char *argv0; static int flag_replace, flag_force; #if DEBUG static int flag_debug; #endif volatile int signal_caught; static void ( *update_window )( struct gwm_window *window ); void ( *window_size )( struct gwm_window *window, int *width, int *height ); void ( *replace_icons )( struct gwm_window *window, int num_icons, int *widths, int *heights, uint32_t **icons, xcb_pixmap_t *bitmaps ); extern 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 ); } extern 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 && sequence - queue_head->sequence < UINT_MAX >> 1 ) { 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_MENU: fputs( "menu", stdout ); client = NULL; break; case WINDOW_MENUITEM: fputs( "menu item", stdout ); client = NULL; 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; case WINDOW_CHILDLESS: fputs( "childless", 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 *const 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 *const 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; } #if USE_RANDR if( have_extension[ EXT_RANDR ] && error->error_code >= extension_error[ EXT_RANDR ] && error->error_code <= extension_error[ EXT_RANDR ] + XCB_RANDR_BAD_MODE ) { static const char *const randr_strings[ XCB_RANDR_BAD_MODE + 1 ] = { "Output", "Crtc", "Mode" }; type = randr_strings[ error->error_code - extension_error[ EXT_RANDR ] ]; } #endif #if USE_RENDER if( have_extension[ EXT_RENDER ] && error->error_code >= extension_error[ EXT_RENDER ] && error->error_code <= extension_error[ EXT_RENDER ] + XCB_RENDER_GLYPH ) { static const char *const render_strings[ XCB_RENDER_GLYPH + 1 ] = { "PictFormat", "Picture", "PictOp", "GlyphSet", "Glyph" }; type = render_strings[ error->error_code - extension_error[ EXT_RENDER ] ]; } #endif 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 *const 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; /* FIXME handle extensions */ 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. */ 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 CONST xcb_timestamp_t event_time( xcb_generic_event_t *ev ) { switch( ev->response_type ) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: return ( (xcb_key_press_event_t *) ev )->time; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: return ( (xcb_button_press_event_t *) ev )->time; case XCB_MOTION_NOTIFY: return ( (xcb_motion_notify_event_t *) ev )->time; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: return ( (xcb_enter_notify_event_t *) ev )->time; case XCB_PROPERTY_NOTIFY: return ( (xcb_property_notify_event_t *) ev )->time; case XCB_SELECTION_CLEAR: return ( (xcb_selection_clear_event_t *) ev )->time; case XCB_SELECTION_REQUEST: return ( (xcb_selection_request_event_t *) ev )->time; case XCB_SELECTION_NOTIFY: return ( (xcb_selection_notify_event_t *) ev )->time; default: return latest_timestamp; } } extern int point_live( int screen, int x, int y ) { #if USE_RANDR if( have_extension[ EXT_RANDR ] ) { int i; for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) { struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ]; if( x >= c->x && x < c->x + c->width && y >= c->y && y < c->y + c->height ) return TRUE; } return FALSE; } #endif return x >= 0 && x < screens[ screen ]->width_in_pixels && y >= 0 && y < screens[ screen ]->height_in_pixels; } extern void make_area_live( int screen, int *x, int *y, int width, int height, int min_x, int min_y ) { if( min_x < 0 || min_x > width ) min_x = width; if( min_y < 0 || min_y > height ) min_y = height; #if USE_RANDR if( have_extension[ EXT_RANDR ] ) { int i, best_x, best_y, best = INT_MAX; for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) { struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ]; int xc = *x, yc = *y, diff; if( min_x ) { if( *x >= c->x + c->width - min_x ) xc = c->x + c->width - min_x - 1; else if( *x < c->x && *x + width < c->x + min_x ) xc = c->x + min_x - width; } if( min_y ) { if( *y >= c->y + c->height - min_y ) yc = c->y + c->height - min_y - 1; else if( *y < c->y && *y + height < c->y + min_y ) yc = c->y + min_y - height; } if( ( diff = abs( xc - *x ) + abs( yc - *y ) ) < best ) { best_x = xc; best_y = yc; best = diff; } } *x = best_x; *y = best_y; return; } #endif if( min_x ) { if( *x >= screens[ screen ]->width_in_pixels - min_x ) *x = screens[ screen ]->width_in_pixels - min_x - 1; else if( *x < 0 && *x + width < min_x ) *x = min_x - width; } if( min_y ) { if( *y >= screens[ screen ]->height_in_pixels - min_y ) *y = screens[ screen ]->height_in_pixels - min_y - 1; else if( *y < 0 && *y + height < min_y ) *y = min_y - height; } } 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 place_window( struct gwm_window *window, int *x, int *y, int width, int height ) { int sx, sy, xoff, yoff; #if defined( UINT64_MAX ) || defined( uint64_t ) uint64_t hash; #else unsigned long hash; #endif #if USE_RANDR if( have_extension[ EXT_RANDR ] ) { int i; unsigned long *scores, chosen, total = 0; scores = alloca( gwm_screens[ window->screen ].num_crtcs * sizeof *scores ); /* Search for CRTCs big enough to hold the window. */ for( i = 0; i < gwm_screens[ window->screen ].num_crtcs; i++ ) { struct gwm_crtc *c = gwm_screens[ window->screen ].crtcs[ i ]; if( width <= c->width && height <= c->height ) total += c->width + c->height - width - height; scores[ i ] = total; } if( total ) { struct gwm_crtc *c; /* Choose from among the candidates. */ hash = window->w * 0xC27E3B59; #if defined( UINT64_MAX ) || defined( uint64_t ) || ULONG_MAX > 0xFFFFFFFFUL hash ^= hash >> 32; #endif chosen = hash % total; for( i = 0; scores[ i ] < chosen; i++ ) ; c = gwm_screens[ window->screen ].crtcs[ i ]; xoff = c->x; yoff = c->y; sx = c->width - width; sy = c->height - height; } else { /* No CRTC is big enough to hold the window. */ xoff = yoff = 0; sx = screens[ window->screen ]->width_in_pixels - width; sy = screens[ window->screen ]->height_in_pixels - height; } } else #endif { xoff = yoff = 0; sx = screens[ window->screen ]->width_in_pixels - width; sy = screens[ window->screen ]->height_in_pixels - height; } if( sx <= 0 ) *x = xoff; else { hash = window->w * 0x9B4A36D1; #if defined( UINT64_MAX ) || defined( uint64_t ) || ULONG_MAX > 0xFFFFFFFFUL hash ^= hash >> 32; #endif *x = xoff + hash % sx; } if( sy <= 0 ) *y = yoff; else { hash = window->w * 0xA6E34925; #if defined( UINT64_MAX ) || defined( uint64_t ) || ULONG_MAX > 0xFFFFFFFFUL hash ^= hash >> 32; #endif *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[ 5 ]; 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; #if USE_RENDER window->u.managed.full_icons = window->u.managed.menu_icons = NULL; window->u.managed.net_wm_icon = FALSE; #endif window->u.managed.name = NULL; window->u.managed.net_wm_name = FALSE; window->u.managed.state = STATE_WITHDRAWN; #if USE_SHAPE window->u.managed.shaped = have_extension[ EXT_SHAPE ] && shape && shape->bounding_shaped; #endif frame->screen = window->screen; frame->type = WINDOW_FRAME; frame->u.frame.child = window; frame->u.frame.button = button; frame->u.frame.decoration = DEC_DEFAULT; for( i = 0; i < NUM_BORDER_REGIONS; i++ ) frame->u.frame.border_regions[ i ] = xcb_generate_id( c ); 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 ] ); update_frame_extents( frame ); translate_child_to_frame( 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; } make_area_live( frame->screen, &frame->u.frame.x, &frame->u.frame.y, frame->u.frame.width, frame->u.frame.height, 8, 8 ); 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_EVENT_MASK_OWNER_GRAB_BUTTON; 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, frame_xb( frame ), 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_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_OWNER_GRAB_BUTTON; xcb_create_window( c, XCB_COPY_FROM_PARENT, button->w, frame->w, 2, 1, button_size( button, FALSE ), button_size( button, FALSE ), button_xb( button ), 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 ); if( frame->u.frame.decoration & DEC_TITLE ) xcb_map_window( c, button->w ); for( i = 0; i < NUM_BORDER_REGIONS; i++ ) { values[ 0 ] = cursors[ i < BR_R ? i : i + 1 ]; /* Temporary positions: these windows will be moved, sized and mapped if necessary in the frame ConfigureNotify handler. */ xcb_create_window( c, 0, frame->u.frame.border_regions[ i ], frame->w, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_CW_CURSOR, values ); } 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_l( frame, FALSE ), frame_t( frame, FALSE ) ); 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( window->u.managed.shaped ) match_managed_shape( window ); #endif /* Tell the client we've relocated their window with respect to the root (see ICCCM 2.0, section 4.2.3); we almost certainly have, between reparenting it and possibly applying a placement policy. */ synthetic_configure_notify( frame ); 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; } static void handle_destroy_window( unsigned int sequence, void *reply, xcb_generic_error_t *error, union callback_param cp ) { if( reply ) free( reply ); if( error ) { show_error( error ); free( error ); } forget_window( cp.p ); } 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; union callback_param cp; 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( frame, &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 ); cp.p = frame; handle_async_reply( xcb_destroy_window_checked( c, frame->w ).sequence, handle_destroy_window, cp ); if( window->u.managed.name ) free( window->u.managed.name ); replace_icons( window, 0, NULL, NULL, NULL, NULL ); forget_window( window ); forget_window( frame->u.frame.button ); /* We can't completely dismiss the frame window yet. Until our DestroyWindow is handled, other events might still arrive for it: in particular, we might see a MapRequest indicating a transition back to the Normal state before the adoption by the root has occurred. */ frame->type = WINDOW_CHILDLESS; } 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 pointer_demux; 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; cp.p = p; 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 const event_handler fake_handlers[ NUM_EXTENDED_EVENTS ] = { 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, /* RRNotify */ NULL /* ShapeNotify */ }, feedback_handlers[ NUM_EXTENDED_EVENTS ] = { 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) */ NULL, /* RRNotify */ NULL /* ShapeNotify */ }; static const event_handler *const handlers[] = { root_handlers, managed_handlers, frame_handlers, button_handlers, menu_handlers, menuitem_handlers, fake_handlers, feedback_handlers, NULL, childless_handlers }; #if USE_RANDR struct crtc_callback { int screen; int crtc; int *ok; }; extern void update_crtc( int screen, int crtc, int width, int height, int x, int y ) { struct gwm_screen *s = gwm_screens + screen; int i; if( width && height ) { for( i = 0; i < s->num_crtcs; i++ ) if( s->crtcs[ i ]->crtc == crtc ) goto found; s->crtcs = xrealloc( s->crtcs, ++s->num_crtcs * sizeof s->crtcs ); s->crtcs[ i ] = xmalloc( sizeof *s->crtcs[ i ] ); found: s->crtcs[ i ]->crtc = crtc; s->crtcs[ i ]->x = x; s->crtcs[ i ]->y = y; s->crtcs[ i ]->width = width; s->crtcs[ i ]->height = height; } else for( i = 0; i < s->num_crtcs; i++ ) if( s->crtcs[ i ]->crtc == crtc ) { free( s->crtcs[ i ] ); s->crtcs[ i ] = s->crtcs[ --s->num_crtcs ]; s->crtcs = xrealloc( s->crtcs, s->num_crtcs * sizeof s->crtcs ); return; } } static INIT void handle_crtc_info( unsigned int sequence, void *reply, xcb_generic_error_t *error, union callback_param cp ) { struct crtc_callback *cc = cp.p; if( error ) { *cc->ok = FALSE; free( error ); } if( reply ) { /* A fixed version of the xcb_randr_get_crtc_info_reply_t definition. Unfortunately XCB's randr protocol was incorrect until commit c8129e1f19ceb2002c9764b94f03564600f13a8f. Rather than checking for a defective randr.h, we simply define the correct structure here. */ struct fixed_xcb_randr_get_crtc_info_reply_t { uint8_t response_type; uint8_t status; uint16_t sequence; uint32_t length; xcb_timestamp_t timestamp; int16_t x; int16_t y; uint16_t width; uint16_t height; uint32_t mode; uint16_t rotation; uint16_t rotations; uint16_t num_outputs; uint16_t num_possible_outputs; } *r = reply; if( r->status == XCB_RANDR_SET_CONFIG_SUCCESS ) update_crtc( cc->screen, cc->crtc, r->width, r->height, r->x, r->y ); else *cc->ok = FALSE; free( reply ); } free( cc ); } #endif static INIT void setup_display( void ) { static INITD const xcb_extension_t *const extensions[ EXTENSIONS_SIZE ] = { #if USE_COMPOSITE &xcb_composite_id, #endif #if USE_DAMAGE &xcb_damage_id, #endif #if USE_RANDR &xcb_randr_id, #endif #if USE_RENDER &xcb_render_id, #endif #if USE_SHAPE &xcb_shape_id, #endif #if USE_XFIXES &xcb_xfixes_id #endif }; 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, values[ 4 ]; 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; #if USE_RANDR xcb_randr_query_version_cookie_t randr_cookie; xcb_randr_get_screen_resources_cookie_t *resources_cookies; #endif table_init( &windows ); table_init( &update_windows ); stack_init( &window_stack ); 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 ); #if USE_RANDR resources_cookies = alloca( num_screens * sizeof *resources_cookies ); #endif for( i = 0; iter.rem; i++, xcb_screen_next( &iter ) ) { char wm_atom_str[ 16 ]; xcb_depth_iterator_t depth_iter; screens[ i ] = iter.data; screen_atom_cookies[ i ] = xcb_intern_atom( c, 0, sprintf( wm_atom_str, "WM_S%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: ; } 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; } } #if USE_RANDR if( have_extension[ EXT_RANDR ] ) randr_cookie = xcb_randr_query_version( c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION ); #endif 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__MOTIF_WM_HINTS ] = atoms[ ATOM__MOTIF_WM_HINTS ]; prop_atoms[ PROP__NET_WM_ICON ] = atoms[ ATOM__NET_WM_ICON ]; prop_atoms[ PROP__NET_WM_NAME ] = atoms[ ATOM__NET_WM_NAME ]; 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__MOTIF_WM_HINTS ] = atoms[ ATOM__MOTIF_WM_HINTS ]; prop_types[ PROP__NET_WM_ICON ] = CARDINAL; prop_types[ PROP__NET_WM_NAME ] = atoms[ ATOM_UTF8_STRING ]; 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 ); } 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_REPLACE, fake_window->w, atoms[ ATOM__NET_WM_NAME ], atoms[ ATOM_UTF8_STRING ], 8, strlen( PACKAGE_STRING ), PACKAGE_STRING ); xcb_change_property( c, XCB_PROP_MODE_REPLACE, fake_window->w, atoms[ ATOM__NET_SUPPORTING_WM_CHECK ], WINDOW, 32, 1, &fake_window->w ); #if USE_RENDER if( have_extension[ EXT_RENDER ] && ( have_extension[ EXT_RENDER ] == !decorate_render_init() ) ) { update_window = render_update_window; window_size = render_window_size; replace_icons = render_replace_icons; } else #endif { decorate_core_init(); update_window = core_update_window; window_size = core_window_size; replace_icons = core_replace_icons; } /* 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 != atoms[ ATOM__NET_WM_NAME ] || 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 ); #if USE_RANDR if( have_extension[ EXT_RANDR ] ) { xcb_randr_query_version_reply_t *randr_reply; if( ( randr_reply = xcb_randr_query_version_reply( c, randr_cookie, NULL ) ) ) { /* We need at least version 1.2 to obtain CRTC information. */ have_extension[ EXT_RANDR ] = randr_reply->major_version > 1 || ( randr_reply->major_version == 1 && randr_reply->minor_version >= 2 ); free( randr_reply ); } else have_extension[ EXT_RANDR ] = 0; } #endif 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_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_OWNER_GRAB_BUTTON; for( i = 0; i < num_screens; i++ ) { #if USE_RANDR if( have_extension[ EXT_RANDR ] ) { xcb_randr_select_input( c, screens[ i ]->root, XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE ); resources_cookies[ i ] = xcb_randr_get_screen_resources( c, screens[ i ]->root ); } #endif /* 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 ); /* Querying the list of children of the root must be atomic with respect to both our SubstructureNotify and SubstructureRedirect event masks taking effect, so grab the server for a moment. */ xcb_grab_server( c ); 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_ungrab_server( c ); values[ 0 ] = 0; xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, atoms[ ATOM__NET_CURRENT_DESKTOP ], CARDINAL, 32, 1, values ); values[ 0 ] = screens[ i ]->width_in_pixels; values[ 1 ] = screens[ i ]->height_in_pixels; xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, atoms[ ATOM__NET_DESKTOP_GEOMETRY ], CARDINAL, 32, 2, values ); values[ 0 ] = 0; values[ 1 ] = 0; xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, atoms[ ATOM__NET_DESKTOP_VIEWPORT ], CARDINAL, 32, 2, values ); values[ 0 ] = 1; xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, atoms[ ATOM__NET_NUMBER_OF_DESKTOPS ], CARDINAL, 32, 1, values ); values[ 0 ] = atoms[ ATOM__NET_WM_ICON ]; values[ 1 ] = atoms[ ATOM__NET_WM_NAME ]; xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, atoms[ ATOM__NET_SUPPORTED ], CARDINAL, 32, 2, values ); values[ 0 ] = fake_window->w; xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, atoms[ ATOM__NET_SUPPORTING_WM_CHECK ], WINDOW, 32, 1, values ); values[ 0 ] = 0; values[ 1 ] = 0; values[ 2 ] = screens[ i ]->width_in_pixels; values[ 3 ] = screens[ i ]->height_in_pixels; xcb_change_property( c, XCB_PROP_MODE_REPLACE, screens[ i ]->root, atoms[ ATOM__NET_WORKAREA ], CARDINAL, 32, 4, values ); } 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( tree_cookies[ num_screens - 1 ].sequence ); #if USE_RANDR if( have_extension[ EXT_RANDR ] ) { int i, ok; unsigned int sequence; for( i = 0; i < num_screens; i++ ) { gwm_screens[ i ].num_crtcs = 0; gwm_screens[ i ].crtcs = NULL; } /* It's essential to have up to date CRTC information before proceeding, because the placement policy when we adopt existing windows depends on it. */ for( i = 0; i < num_screens; i++ ) { xcb_randr_get_screen_resources_reply_t *r; retry_race: r = xcb_randr_get_screen_resources_reply( c, resources_cookies[ i ], NULL ); if( r ) { int crtc; union callback_param cp; uint32_t *crtcs = xcb_randr_get_screen_resources_crtcs( r ); for( crtc = 0; crtc < r->num_crtcs; crtc++ ) { struct crtc_callback *cc = xmalloc( sizeof *cc ); cc->screen = i; cc->crtc = crtcs[ crtc ]; cc->ok = &ok; cp.p = cc; handle_async_reply( sequence = xcb_randr_get_crtc_info( c, crtcs[ crtc ], r->config_timestamp ).sequence, handle_crtc_info, cp ); } free( r ); } ok = TRUE; sync_with_callback( sequence ); if( !ok ) { resources_cookies[ i ] = xcb_randr_get_screen_resources( c, screens[ i ]->root ); xcb_flush( c ); /* BLOCK */ goto retry_race; } } } #endif 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; stack_insert_singleton( &window_stack, screens[ i ]->root | STACK_END ); 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 ); if( r->children_len ) stack_insert_above( &window_stack, children[ 0 ], screens[ i ]->root | STACK_END ); for( j = 1; j < r->children_len; j++ ) stack_insert_above( &window_stack, children[ j ], children[ j - 1 ] ); 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 ) normal_to_iconic( window ); } 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 ] ); } /* FIXME and all the EWMH client messages: _NET_CLOSE_WINDOW _NET_MOVE_RESIZE_WINDOW _NET_WM_MOVERESIZE _NET_RESTACK_WINDOW _NET_REQUEST_FRAME_EXTENTS */ } static void handle_events( void ) { xcb_generic_event_t *ev; /* FIXME Should read as many events as possible (loop on xcb_poll_for_event), and queue them... might be able to elide some event processing that way if later events supercede earlier ones. */ 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 = pointer_demux ? pointer_demux : ( (xcb_button_press_event_t *) ev )->event; latest_timestamp = ( (xcb_button_press_event_t *) ev )->time; break; case XCB_BUTTON_RELEASE: w = pointer_demux ? pointer_demux : ( (xcb_button_release_event_t *) ev )->event; latest_timestamp = ( (xcb_button_release_event_t *) ev )->time; break; case XCB_MOTION_NOTIFY: w = pointer_demux ? pointer_demux : ( (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 = pointer_demux ? pointer_demux : ( (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_RANDR if( event_base_type == extension_event[ EXT_RANDR ] + XCB_RANDR_NOTIFY && ( (xcb_randr_notify_event_t *) ev )->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE ) { w = ( (xcb_randr_notify_event_t *) ev )->u.cc.window; latest_timestamp = ( (xcb_randr_notify_event_t *) ev )->u.cc.timestamp; event_type = RANDR_CRTC_CHANGE_NOTIFY; break; } #endif #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" ); continue; } 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_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 ); } /* FIXME other features: - make override-redirect windows translucent - when in the window menu, turn all windows translucent except for the selected item (temporarily raise that one?) - root background/cursor? (no; xsetroot for non composite case) - standard colourmaps? (no; xstdcmap does that) - DPMS - add debugging structure to detect memory leaks, and maybe even correlating X protocol errors to request call sites - other extensions... XINERAMA? RANDR? RandR should send ConfigureNotify to the root when resolution changes, so don't need the extension... test it. */ 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; xcb_generic_event_t *ev; for( i = 0; i < num_screens; i++ ) { xcb_window_t w = screens[ i ]->root | STACK_END; do { struct gwm_window *window = lookup_window( w ); xcb_window_t next = stack_lookup( &window_stack, w )->higher_window; if( window && window->type == WINDOW_FRAME ) unmanage_window( window->u.frame.child ); w = next; } while( w != ( screens[ i ]->root | STACK_END ) ); } /* 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 ); n = 0; for( i = 0; i < num_screens; i++ ) { xcb_change_window_attributes( c, screens[ i ]->root, XCB_CW_EVENT_MASK, &n ); xcb_delete_property( c, screens[ i ]->root, atoms[ ATOM__NET_CURRENT_DESKTOP ] ); xcb_delete_property( c, screens[ i ]->root, atoms[ ATOM__NET_DESKTOP_GEOMETRY ] ); xcb_delete_property( c, screens[ i ]->root, atoms[ ATOM__NET_DESKTOP_VIEWPORT ] ); xcb_delete_property( c, screens[ i ]->root, atoms[ ATOM__NET_NUMBER_OF_DESKTOPS ] ); xcb_delete_property( c, screens[ i ]->root, atoms[ ATOM__NET_SUPPORTED ] ); xcb_delete_property( c, screens[ i ]->root, atoms[ ATOM__NET_SUPPORTING_WM_CHECK ] ); xcb_delete_property( c, screens[ i ]->root, atoms[ ATOM__NET_WORKAREA ] ); } #if USE_RENDER if( have_extension[ EXT_RENDER ] ) decorate_render_done(); else #endif decorate_core_done(); /* 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 ); /* We have now received all events that preceded our change to the root event masks, and we won't be getting any more redirected requests. Run through the event queue one last time, and handle any that are still waiting. */ while( ( ev = xcb_poll_for_event( c ) ) ) { #if DEBUG if( flag_debug && ev ) show_event( ev ); #endif switch( ev->response_type ) { case 0: /* Error */ show_error( (xcb_generic_error_t *) ev ); break; case XCB_MAP_REQUEST: xcb_map_window( c, ( ( xcb_map_request_event_t *) ev )->window ); break; case XCB_CONFIGURE_REQUEST: withdrawn_configure_request( NULL, (xcb_configure_request_event_t *) ev ); break; case XCB_CIRCULATE_REQUEST: root_circulate_request( NULL, (xcb_circulate_request_event_t *) ev ); break; default: /* Do nothing. We're about to go away. Even client messages such as WM_CHANGE_STATE should be ignored; all windows have been forced into the Normal state whether they like it or not. */ break; } free( ev ); } 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 ); stack_destroy( &window_stack ); cleanup_keyboard(); free( screens ); free( gwm_screens ); cleanup_utf8(); #if HAVE_MTRACE muntrace(); #endif #endif } static INIT 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 ); } static INIT void init( int argc, char *argv[] ) { int i, j; struct sigaction sa; static int flag_help, flag_version; static INITD 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 ] ); exit( 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 ] ); exit( 1 ); } } } else { unknown( argv[ i ] ); exit( 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 ); exit( 0 ); } if( flag_version ) { puts( PACKAGE_STRING "\nPlease send bug reports to <" PACKAGE_BUGREPORT ">." ); exit( 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(); } extern int main( int argc, char *argv[] ) { int which_signal; init( argc, argv ); #if HAVE_INIT_SECTION { extern char init_start, init_end; munmap( &init_start, &init_end - &init_start ); } #endif 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 > 0 ) { raise( which_signal ); return 1; } return 0; } /* FIXME xv moves when changing files (or pressing "n")... investigate. */