Initial commit (gwm-basic 1.0).
[gwm.git] / gwm.c
blobb5a0d7584c98a8ffd6e1e925b57b3f8b0fded8c6
1 /*
2 * gwm.c
4 * Part of gwm, the Gratuitous Window Manager,
5 * by Gary Wong, <gtw@gnu.org>.
7 * Copyright (C) 2009 Gary Wong
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of version 3 of the GNU General Public License as
11 * published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * $Id$
24 #include <config.h>
26 #include <assert.h>
27 #include <errno.h>
28 #if HAVE_MCHECK_H
29 #include <mcheck.h>
30 #endif
31 #if HAVE_POLL_H
32 #include <poll.h>
33 #endif
34 #include <signal.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #if HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif
42 #include <sys/wait.h>
43 #if USE_SHAPE
44 #include <xcb/shape.h>
45 #endif
46 #include <xcb/xcb.h>
47 #include <xcb/xcbext.h>
49 #include "gwm.h"
51 #include "button.h"
52 #include "decorate-core.h"
53 #include "frame.h"
54 #include "keyboard.h"
55 #include "managed.h"
56 #include "root.h"
57 #include "utf8.h"
58 #include "window-table.h"
60 xcb_connection_t *c;
61 xcb_timestamp_t latest_timestamp;
63 xcb_atom_t atoms[ NUM_ATOMS ];
65 static const char *atom_names[ NUM_ATOMS ] = {
66 "COMPOUND_TEXT",
67 "MANAGER",
68 "UTF8_STRING",
69 "VERSION",
70 "WM_CHANGE_STATE",
71 "WM_COLORMAP_NOTIFY",
72 "WM_COLORMAP_WINDOWS",
73 "WM_DELETE_WINDOW",
74 "WM_PROTOCOLS",
75 "WM_STATE",
76 "WM_TAKE_FOCUS"
79 xcb_atom_t prop_atoms[ NUM_PROPS ];
80 xcb_atom_t prop_types[ NUM_PROPS ];
82 static const xcb_extension_t *extensions[ EXTENSIONS_SIZE ] = {
83 #if USE_SHAPE
84 &xcb_shape_id,
85 #endif
88 int have_extension[ EXTENSIONS_SIZE ];
89 uint8_t extension_event[ EXTENSIONS_SIZE ],
90 extension_error[ EXTENSIONS_SIZE ];
92 int num_screens;
93 xcb_screen_t **screens;
94 struct gwm_screen *gwm_screens;
96 xcb_cursor_t cursors[ NUM_CURSORS ];
98 struct gwm_window *fake_window, *focus_frame;
100 const char *argv0;
101 static int flag_replace, flag_force;
102 #if DEBUG
103 static int flag_debug;
104 #endif
106 static volatile int signal_caught;
108 static void ( *update_window )( struct gwm_window *window );
110 static FORMAT( printf, 1, 2 ) void warning( char *format, ... ) {
112 va_list val;
114 va_start( val, format );
116 fprintf( stderr, "%s: warning: ", argv0 );
117 vfprintf( stderr, format, val );
118 putc( '\n', stderr );
120 va_end( val );
123 static FORMAT( printf, 1, 2 ) NORETURN void fatal( char *format, ... ) {
125 va_list val;
127 va_start( val, format );
129 fprintf( stderr, "%s: ", argv0 );
130 vfprintf( stderr, format, val );
131 putc( '\n', stderr );
133 va_end( val );
135 exit( 1 ); /* don't attempt an orderly shutdown */
138 extern MALLOC void *xmalloc( size_t size ) {
140 void *p;
142 /* It would be nice to clean up gracefully on allocation errors.
143 Unfortunately, doing so would require transmitting many requests
144 to the X server, which would require further allocations which
145 are likely to fail. Simply aborting with an error message is
146 therefore probably about as effective, and much simpler. */
148 if( !( p = malloc( size ) ) ) {
149 perror( argv0 );
151 exit( 1 );
154 return p;
157 extern void *xrealloc( void *p, size_t size ) {
159 if( !( p = realloc( p, size ) ) ) {
160 perror( argv0 );
162 exit( 1 );
165 return p;
168 extern MALLOC void *xcalloc( size_t number, size_t size ) {
170 void *p;
172 if( !( p = calloc( number, size ) ) ) {
173 perror( argv0 );
175 exit( 1 );
178 return p;
181 static void catch_signal( int n ) {
183 struct sigaction sa;
185 signal_caught = n;
187 sa.sa_handler = SIG_DFL;
188 sigemptyset( &sa.sa_mask );
189 sa.sa_flags = 0;
190 sigaction( SIGHUP, &sa, NULL );
191 sigaction( SIGINT, &sa, NULL );
192 sigaction( SIGTERM, &sa, NULL );
195 static void alarm_signal( int n ) {
197 signal_caught = n;
200 static void child_signal( int n ) {
202 while( waitpid( -1, 0, WNOHANG ) > 0 )
206 static struct async_callback {
207 unsigned int sequence;
208 void ( *callback )( unsigned int sequence, void *reply,
209 xcb_generic_error_t *error, union callback_param p );
210 union callback_param p;
211 struct async_callback *next;
212 } *queue_head, *queue_tail;
214 static void check_async_callbacks( void ) {
216 void *reply;
217 xcb_generic_error_t *error;
219 while( queue_head && xcb_poll_for_reply( c, queue_head->sequence,
220 &reply, &error ) ) {
221 struct async_callback *entry = queue_head;
223 if( !( queue_head = entry->next ) )
224 queue_tail = NULL;
226 entry->callback( entry->sequence, reply, error, entry->p );
228 free( entry );
232 extern void sync_with_callback( unsigned int sequence ) {
234 while( queue_head && (int) sequence - (int) queue_head->sequence >= 0 ) {
235 struct async_callback *entry = queue_head;
236 xcb_generic_error_t *error;
237 void *reply = xcb_wait_for_reply( c, entry->sequence, &error );
239 if( !( queue_head = entry->next ) )
240 queue_tail = NULL;
242 entry->callback( entry->sequence, reply, error, entry->p );
244 free( entry );
248 #if DEBUG
249 static void show_window( char *label, xcb_window_t w ) {
251 /* We don't want to use lookup_window here, since mere debugging
252 output shouldn't trigger the side effect of finishing incomplete
253 windows. */
254 struct gwm_window *window = table_lookup( &windows, w );
256 printf( " %s %08X (", label, w );
258 if( w == XCB_NONE )
259 fputs( "None", stdout );
260 else if( window ) {
261 struct gwm_window *client;
262 switch( window->type ) {
263 case WINDOW_ROOT:
264 printf( "root, screen %d", window->screen );
265 client = NULL;
266 break;
268 case WINDOW_MANAGED:
269 client = window;
270 break;
272 case WINDOW_FRAME:
273 fputs( "frame, ", stdout );
274 client = window->u.frame.child;
275 break;
277 case WINDOW_BUTTON:
278 fputs( "button, ", stdout );
279 client = window->u.button.frame->u.frame.child;
280 break;
282 case WINDOW_FAKE:
283 fputs( "fake", stdout );
284 client = NULL;
285 break;
287 case WINDOW_FEEDBACK:
288 printf( "feedback, screen %d", window->screen );
289 client = NULL;
290 break;
292 case WINDOW_INCOMPLETE:
293 fputs( "incomplete", stdout );
294 client = NULL;
295 break;
298 if( client )
299 fputs( client->u.managed.name ? client->u.managed.name : "unnamed",
300 stdout );
301 } else
302 fputs( "unknown", stdout );
304 putchar( ')' );
305 putchar( '\n' );
308 static void show_atom( char *label, xcb_atom_t atom ) {
310 static const char *predefined_names[ 69 ] = {
311 "None", "PRIMARY", "SECONDARY", "ARC", "ATOM", "BITMAP", "CARDINAL",
312 "COLORMAP", "CURSOR", "CUT_BUFFER0", "CUT_BUFFER1", "CUT_BUFFER2",
313 "CUT_BUFFER3", "CUT_BUFFER4", "CUT_BUFFER5", "CUT_BUFFER6",
314 "CUT_BUFFER7", "DRAWABLE", "FONT", "INTEGER", "PIXMAP", "POINT",
315 "RECTANGLE", "RESOURCE_MANAGER", "RGB_COLOR_MAP", "RGB_BEST_MAP",
316 "RGB_BLUE_MAP", "RGB_DEFAULT_MAP", "RGB_GRAY_MAP", "RGB_GREEN_MAP",
317 "RGB_RED_MAP", "STRING", "VISUALID", "WINDOW", "WM_COMMAND",
318 "WM_HINTS", "WM_CLIENT_MACHINE", "WM_ICON_NAME", "WM_ICON_SIZE",
319 "WM_NAME", "WM_NORMAL_HINTS", "WM_SIZE_HINTS", "WM_ZOOM_HINTS",
320 "MIN_SPACE", "NORM_SPACE", "MAX_SPACE", "END_SPACE", "SUPERSCRIPT_X",
321 "SUPERSCRIPT_Y", "SUBSCRIPT_X", "SUBSCRIPT_Y", "UNDERLINE_POSITION",
322 "UNDERLINE_THICKNESS", "STRIKEOUT_ASCENT", "STRIKEOUT_DESCENT",
323 "ITALIC_ANGLE", "X_HEIGHT", "QUAD_WIDTH", "WEIGHT", "POINT_SIZE",
324 "RESOLUTION", "COPYRIGHT", "NOTICE", "FONT_NAME", "FAMILY_NAME",
325 "FULL_NAME", "CAP_HEIGHT", "WM_CLASS", "WM_TRANSIENT_FOR"
327 const char *name;
329 if( atom < 69 )
330 name = predefined_names[ atom ];
331 else {
332 int i;
334 for( i = 0; i < NUM_ATOMS; i++ )
335 if( atoms[ i ] == atom ) {
336 name = atom_names[ i ];
337 break;
340 if( i == NUM_ATOMS )
341 name = "unknown";
344 printf( " %s %d (%s)\n", label, atom, name );
346 #endif
348 extern void show_error( xcb_generic_error_t *error ) {
350 xcb_value_error_t *verror = (xcb_value_error_t *) error;
351 #if DEBUG
352 static const char *type_strings[ XCB_IMPLEMENTATION + 1 ] = {
353 "Unknown", "Request", "Value", "Window", "Pixmap", "Atom", "Cursor",
354 "Font", "Match", "Drawable", "Access", "Alloc", "Colormap", "GContext",
355 "IDChoice", "Name", "Length", "Implementation"
357 const char *type, *bad_value_str;
358 char num[ 32 ], bad_num[ 32 ];
360 if( error->error_code <= XCB_IMPLEMENTATION )
361 type = type_strings[ error->error_code ];
362 else {
363 sprintf( num, "%d", error->error_code );
364 type = num;
367 switch( error->error_code ) {
368 case XCB_VALUE:
369 case XCB_WINDOW:
370 case XCB_PIXMAP:
371 case XCB_ATOM:
372 case XCB_CURSOR:
373 case XCB_FONT:
374 case XCB_DRAWABLE:
375 case XCB_COLORMAP:
376 case XCB_G_CONTEXT:
377 case XCB_ID_CHOICE:
378 sprintf( bad_num, ", val %X", verror->bad_value );
379 bad_value_str = bad_num;
380 break;
382 default:
383 bad_value_str = "";
384 break;
387 warning( "unexpected error (type %s, sequence %d; opcode %d %d%s)", type,
388 error->sequence, verror->major_opcode, verror->minor_opcode,
389 bad_value_str );
390 #else
391 warning( "unexpected error (type %d, sequence %d; opcode %d %d)",
392 error->error_code, error->sequence,
393 verror->major_opcode, verror->minor_opcode );
394 #endif
397 #if DEBUG
398 static void show_event( xcb_generic_event_t *generic ) {
400 static const char *event_names[ XCB_MAPPING_NOTIFY + 1 ] = {
401 "Error", "Reply", "KeyPress", "KeyRelease", "ButtonPress",
402 "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify",
403 "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExposure",
404 "NoExposure", "VisibilityNotify", "CreateNotify", "DestroyNotify",
405 "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify",
406 "ConfigureNotify", "ConfigureRequest", "GravityNotify",
407 "ResizeRequest", "CirculateNotify", "CirculateRequest",
408 "PropertyNotify", "SelectionClear", "SelectionRequest",
409 "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify"
411 union event {
412 xcb_key_press_event_t kbm; /* KeyPress, KeyRelease, ButtonPress,
413 ButtonRelease, MotionNotify */
414 xcb_enter_notify_event_t el; /* EnterNotify, LeaveNotify */
415 xcb_focus_in_event_t f; /* FocusIn, FocusOut */
416 xcb_keymap_notify_event_t km; /* KeymapNotify */
417 xcb_expose_event_t e; /* Expose */
418 xcb_graphics_exposure_event_t g; /* GraphicsExposure */
419 xcb_no_exposure_event_t n; /* NoExposure */
420 xcb_visibility_notify_event_t v; /* VisibilityNotify */
421 xcb_create_notify_event_t c; /* CreateNotify */
422 xcb_destroy_notify_event_t d; /* DestroyNotify */
423 xcb_unmap_notify_event_t u; /* UnmapNotify */
424 xcb_map_notify_event_t m; /* MapNotify */
425 xcb_map_request_event_t mr; /* MapRequest */
426 xcb_reparent_notify_event_t r; /* ReparentNotify */
427 xcb_configure_notify_event_t con; /* ConfigureNotify */
428 xcb_configure_request_event_t cr; /* ConfigureRequest */
429 xcb_gravity_notify_event_t gn; /* GravityNotify */
430 xcb_resize_request_event_t rr; /* ResizeRequest */
431 xcb_circulate_notify_event_t cir; /* CirculateNotify */
432 xcb_circulate_request_event_t cirr; /* CirculateRequest */
433 xcb_property_notify_event_t p; /* PropertyNotify */
434 xcb_selection_clear_event_t sc; /* SelectionClear */
435 xcb_selection_request_event_t sr; /* SelectionRequest */
436 xcb_selection_notify_event_t sn; /* SelectionNotify */
437 xcb_colormap_notify_event_t cm; /* ColormapNotify */
438 xcb_client_message_event_t cli; /* ClientMessage */
439 xcb_mapping_notify_event_t map; /* MappingNotify */
440 } *ev = (union event *) generic;
441 int type = generic->response_type & 0x7F;
442 int i;
444 if( !( generic->response_type & ~SEND_EVENT_MASK ) ) {
445 show_error( (xcb_generic_error_t *) generic );
447 return;
450 printf( "Event %d (%s%s)\n", generic->response_type,
451 type <= XCB_MAPPING_NOTIFY ? event_names[ type ] : "Unknown",
452 generic->response_type & SEND_EVENT_MASK ? ", synthetic" : "" );
454 if( type < XCB_KEY_PRESS || type > XCB_MAPPING_NOTIFY )
455 return;
457 if( type != XCB_KEYMAP_NOTIFY )
458 printf( " Sequence %X\n", generic->full_sequence );
460 switch( type ) {
461 case XCB_KEY_PRESS:
462 case XCB_KEY_RELEASE:
463 case XCB_BUTTON_PRESS:
464 case XCB_BUTTON_RELEASE:
465 case XCB_MOTION_NOTIFY:
466 show_window( "Root", ev->kbm.root );
467 show_window( "Event", ev->kbm.event );
468 show_window( "Child", ev->kbm.child );
469 printf( " Same-screen %s\n"
470 " Root x/y %d, %d\n"
471 " Event x/y %d, %d\n"
472 " Detail %d\n"
473 " State %04X\n"
474 " Time %d\n",
475 ev->kbm.same_screen ? "Yes" : "No",
476 ev->kbm.root_x, ev->kbm.root_y,
477 ev->kbm.event_x, ev->kbm.event_y,
478 ev->kbm.detail, ev->kbm.state, ev->kbm.time );
479 break;
481 case XCB_ENTER_NOTIFY:
482 case XCB_LEAVE_NOTIFY:
483 show_window( "Root", ev->el.root );
484 show_window( "Event", ev->el.event );
485 show_window( "Child", ev->el.child );
486 printf( " Same-screen %s\n"
487 " Root x/y %d, %d\n"
488 " Event x/y %d, %d\n"
489 " Mode %d\n"
490 " Detail %d\n"
491 " Focus %d\n"
492 " State %04X\n"
493 " Time %d\n",
494 ev->el.same_screen_focus & 2 ? "Yes" : "No",
495 ev->el.root_x, ev->el.root_y,
496 ev->el.event_x, ev->el.event_y,
497 ev->el.mode, ev->el.detail, ev->el.same_screen_focus & 1,
498 ev->el.state, ev->el.time );
499 break;
501 case XCB_FOCUS_IN:
502 case XCB_FOCUS_OUT:
503 show_window( "Event", ev->f.event );
504 printf( " Mode %d\n"
505 " Detail %d\n",
506 ev->f.mode, ev->f.detail );
508 case XCB_KEYMAP_NOTIFY:
509 for( i = 0; i < 0xF8; i++ )
510 if( ev->km.keys[ i >> 3 ] & ( 1 << ( i & 7 ) ) )
511 printf( " key %d down\n", i );
513 break;
515 case XCB_EXPOSE:
516 show_window( "Window", ev->e.window );
517 printf( " x/y %d, %d\n"
518 " width/height %d x %d\n"
519 " Count %d\n",
520 ev->e.x, ev->e.y, ev->e.width, ev->e.height, ev->e.count );
521 break;
523 case XCB_GRAPHICS_EXPOSURE:
524 show_window( "Drawable", ev->g.drawable );
525 printf( " x/y %d, %d\n"
526 " width/height %d x %d\n"
527 " Count %d\n"
528 " Opcode %d, %d\n",
529 ev->g.x, ev->g.y, ev->g.width, ev->g.height, ev->g.count,
530 ev->g.major_opcode, ev->g.minor_opcode );
531 break;
533 case XCB_NO_EXPOSURE:
534 show_window( "Drawable", ev->n.drawable );
535 printf( " Opcode %d, %d\n",
536 ev->n.major_opcode, ev->n.minor_opcode );
537 break;
539 case XCB_VISIBILITY_NOTIFY:
540 show_window( "Window", ev->v.window );
541 printf( " State %d\n", ev->v.state );
542 break;
544 case XCB_CREATE_NOTIFY:
545 show_window( "Parent", ev->c.parent );
546 show_window( "Window", ev->c.window );
547 printf( " x/y %d, %d\n"
548 " width/height %d x %d\n"
549 " Border width %d\n"
550 " Override redirect %s\n",
551 ev->c.x, ev->c.y,
552 ev->c.width, ev->c.height,
553 ev->c.border_width,
554 ev->c.override_redirect ? "Yes" : "No" );
555 break;
557 case XCB_DESTROY_NOTIFY:
558 show_window( "Event", ev->d.event );
559 show_window( "Window", ev->d.window );
560 break;
562 case XCB_UNMAP_NOTIFY:
563 show_window( "Event", ev->u.event );
564 show_window( "Window", ev->u.window );
565 printf( " From configure: %s\n",
566 ev->u.from_configure ? "Yes" : "No" );
567 break;
569 case XCB_MAP_NOTIFY:
570 show_window( "Event", ev->m.event );
571 show_window( "Window", ev->m.window );
572 printf( " Override redirect: %s\n",
573 ev->m.override_redirect ? "Yes" : "No" );
574 break;
576 case XCB_MAP_REQUEST:
577 show_window( "Parent", ev->mr.parent );
578 show_window( "Window", ev->mr.window );
579 break;
581 case XCB_REPARENT_NOTIFY:
582 show_window( "Event", ev->r.event );
583 show_window( "Window", ev->r.window );
584 show_window( "Parent", ev->r.parent );
585 printf( " x/y %d, %d\n"
586 " Override redirect %s\n",
587 ev->r.x, ev->r.y,
588 ev->r.override_redirect ? "Yes" : "No" );
589 break;
591 case XCB_CONFIGURE_NOTIFY:
592 show_window( "Event", ev->con.event );
593 show_window( "Window", ev->con.window );
594 printf( " x/y %d, %d\n"
595 " width/height %d x %d\n"
596 " Border width %d\n",
597 ev->con.x, ev->con.y,
598 ev->con.width, ev->con.height,
599 ev->con.border_width );
600 show_window( "Above sibling", ev->con.above_sibling );
601 printf( " Override redirect %s\n",
602 ev->con.override_redirect ? "Yes" : "No" );
603 break;
605 case XCB_CONFIGURE_REQUEST:
606 show_window( "Parent", ev->cr.parent );
607 show_window( "Window", ev->cr.window );
608 printf( " x/y %d, %d\n"
609 " width/height %d x %d\n"
610 " Border width %d\n",
611 ev->cr.x, ev->cr.y,
612 ev->cr.width, ev->cr.height,
613 ev->cr.border_width );
614 show_window( "Sibling", ev->cr.sibling );
615 printf( " Stack mode %d\n"
616 " Value mask %04X\n",
617 ev->cr.stack_mode, ev->cr.value_mask );
618 break;
620 case XCB_GRAVITY_NOTIFY:
621 show_window( "Event", ev->gn.event );
622 show_window( "Window", ev->gn.window );
623 printf( " x/y %d, %d\n",
624 ev->gn.x, ev->gn.y );
625 break;
627 case XCB_RESIZE_REQUEST:
628 show_window( "Window", ev->rr.window );
629 printf( " width/height %d x %d\n",
630 ev->rr.width, ev->rr.height );
631 break;
633 case XCB_CIRCULATE_NOTIFY:
634 show_window( "Event", ev->cir.event );
635 show_window( "Window", ev->cir.window );
636 printf( " Place %d\n", ev->cir.place );
637 break;
639 case XCB_CIRCULATE_REQUEST:
640 show_window( "Parent", ev->cirr.event );
641 show_window( "Window", ev->cirr.window );
642 printf( " Place %d\n", ev->cirr.place );
643 break;
645 case XCB_PROPERTY_NOTIFY:
646 show_window( "Window", ev->p.window );
647 show_atom( "Atom", ev->p.atom );
648 printf( " State %d\n"
649 " Time %d\n",
650 ev->p.state, ev->p.time );
651 break;
653 case XCB_SELECTION_CLEAR:
654 show_window( "Owner", ev->sc.owner );
655 show_atom( "Selection", ev->sc.selection );
656 printf( " Time %d\n", ev->sc.time );
657 break;
659 case XCB_SELECTION_REQUEST:
660 show_window( "Owner", ev->sr.owner );
661 show_atom( "Selection", ev->sr.selection );
662 show_atom( "Target", ev->sr.target );
663 show_atom( "Property", ev->sr.property );
664 show_window( "Requestor", ev->sr.requestor );
665 printf( " Time %d\n", ev->sr.time );
666 break;
668 case XCB_SELECTION_NOTIFY:
669 show_window( "Requestor", ev->sn.requestor );
670 show_atom( "Selection", ev->sn.selection );
671 show_atom( "Target", ev->sn.target );
672 show_atom( "Property", ev->sn.property );
673 printf( " Time %d\n", ev->sn.time );
674 break;
676 case XCB_COLORMAP_NOTIFY:
677 show_window( "Window", ev->cm.window );
678 printf( " Colormap %d\n"
679 " New %s\n"
680 " State %d\n",
681 ev->cm.colormap,
682 ev->cm._new ? "Yes" : "No",
683 ev->cm.state );
684 break;
686 case XCB_CLIENT_MESSAGE:
687 show_window( "Window", ev->cli.window );
688 show_atom( "Type", ev->cli.type );
689 printf( " Format %d\n", ev->cli.format );
691 switch( ev->cli.format ) {
692 case 8:
693 for( i = 0; i < 20; i++ )
694 printf( " %2d %02X\n", i, ev->cli.data.data8[ i ] );
695 break;
697 case 16:
698 for( i = 0; i < 10; i++ )
699 printf( " %d %04X\n", i, ev->cli.data.data16[ i ] );
700 break;
702 case 32:
703 for( i = 0; i < 5; i++ )
704 printf( " %d %08X\n", i, ev->cli.data.data32[ i ] );
705 break;
708 break;
710 case XCB_MAPPING_NOTIFY:
711 printf( " Request %d\n", ev->map.request );
712 if( ev->map.request == XCB_MAPPING_KEYBOARD )
713 printf( " First keycode %d\n"
714 " Count %d",
715 ev->map.first_keycode, ev->map.count );
716 break;
719 #endif
721 extern xcb_generic_event_t *wait_for_event( void ) {
723 xcb_generic_event_t *ev;
724 sigset_t sigs, oldsigs;
725 int fd = xcb_get_file_descriptor( c );
726 #if HAVE_PPOLL
727 struct pollfd pfd;
728 #else
729 fd_set fds;
730 #endif
732 for(;;) {
733 if( xcb_connection_has_error( c ) )
734 return NULL;
736 ev = xcb_poll_for_event( c );
738 #if DEBUG
739 if( flag_debug && ev )
740 show_event( ev );
741 #endif
743 check_async_callbacks(); /* must poll for events first, since
744 polling replies won't dequeue them */
745 if( ev )
746 return ev;
748 if( update_windows.used ) {
749 struct gwm_window *update = update_windows.values[ 0 ];
751 update_window( update );
752 window_update_done( update );
754 continue;
757 xcb_flush( c );
759 sigemptyset( &sigs );
760 sigaddset( &sigs, SIGHUP );
761 sigaddset( &sigs, SIGINT );
762 sigaddset( &sigs, SIGTERM );
764 sigprocmask( SIG_BLOCK, &sigs, &oldsigs );
766 if( signal_caught ) {
767 sigprocmask( SIG_SETMASK, &oldsigs, NULL );
769 return NULL;
772 #if HAVE_PPOLL
773 pfd.fd = fd;
774 pfd.events = POLLIN;
776 if( ppoll( &pfd, 1, NULL, &oldsigs ) < 0 && errno != EINTR )
777 perror( "ppoll" );
778 #else
779 FD_ZERO( &fds );
780 FD_SET( fd, &fds );
782 if( pselect( fd + 1, &fds, NULL, NULL, NULL, &oldsigs ) < 0 &&
783 errno != EINTR )
784 perror( "pselect" );
785 #endif
787 sigprocmask( SIG_SETMASK, &oldsigs, NULL );
791 extern void handle_async_reply( unsigned int sequence,
792 void ( *callback )( unsigned int sequence,
793 void *reply,
794 xcb_generic_error_t *error,
795 union callback_param p ),
796 union callback_param p ) {
798 struct async_callback *entry = xmalloc( sizeof *entry );
800 entry->sequence = sequence;
801 entry->callback = callback;
802 entry->p = p;
803 entry->next = NULL;
805 if( queue_tail )
806 queue_tail = queue_tail->next = entry;
807 else
808 queue_head = queue_tail = entry;
811 static void error_callback( unsigned int sequence, void *reply,
812 xcb_generic_error_t *error,
813 union callback_param p ) {
815 unsigned ignore = p.l;
817 if( reply )
818 free( reply );
820 if( !error )
821 return; /* no error */
823 if( !( ignore & ( 1 << ( error->error_code - 1 ) ) ) )
824 show_error( error ); /* an error we didn't expect */
826 free( error );
829 extern void handle_error_reply( xcb_void_cookie_t cookie, unsigned ignore ) {
831 union callback_param p;
833 p.l = ignore;
835 handle_async_reply( cookie.sequence, error_callback, p );
838 static void install_colormap( int screen, xcb_colormap_t cmap,
839 xcb_timestamp_t t ) {
841 if( (int) t - (int) gwm_screens[ screen ].cmap_time > 0 ) {
842 gwm_screens[ screen ].cmap_time = t;
844 if( gwm_screens[ screen ].cmap != cmap )
845 xcb_install_colormap( c, gwm_screens[ screen ].cmap = cmap );
849 struct cmap_callback {
850 int screen;
851 xcb_timestamp_t t;
854 static void handle_get_window_attributes( unsigned int sequence, void *reply,
855 xcb_generic_error_t *error,
856 union callback_param cp ) {
858 struct cmap_callback *cc = cp.p;
860 if( error ) {
861 /* Ignore Window errors, since we were given the ID by a client
862 and we don't trust them to get it right. */
863 if( error->error_code != XCB_WINDOW )
864 show_error( error );
866 free( error );
869 if( reply ) {
870 xcb_get_window_attributes_reply_t *r = reply;
872 install_colormap( cc->screen, r->colormap ? r->colormap :
873 screens[ cc->screen ]->default_colormap, cc->t );
875 free( reply );
878 free( cc );
881 /* Install a window's colormap on a screen, or the default colormap if
882 unspecified. */
883 extern void install_window_colormap( int screen, struct gwm_window *window,
884 xcb_timestamp_t t ) {
886 assert( !window || window->type == WINDOW_MANAGED );
888 if( window && window->u.managed.cmap_window ) {
889 union callback_param cp;
890 struct cmap_callback *cc = xmalloc( sizeof *cc );
892 cc->screen = screen;
893 cc->t = t;
894 cp.p = cc;
895 handle_async_reply( xcb_get_window_attributes(
896 c, window->u.managed.cmap_window ).sequence,
897 handle_get_window_attributes, cp );
898 } else
899 install_colormap( screen, window && window->u.managed.cmap ?
900 window->u.managed.cmap :
901 screens[ screen ]->default_colormap, t );
904 /* Return TRUE iff an event is the only button pressed (and so would
905 initiate a passive grab). */
906 extern CONST int initial_press( xcb_button_press_event_t *ev ) {
908 return !( ev->state & 0x1F00 );
911 /* Return TRUE iff an event is the release of the last button (and so would
912 terminate a passive grab). */
913 extern CONST int final_release( xcb_button_release_event_t *ev ) {
915 return ( ev->state & 0x1F00 ) == ( 0x80 << ev->detail );
918 extern void translate_child_to_frame( int *fx, int *fy, int *fwidth,
919 int *fheight, int cx, int cy,
920 int cwidth, int cheight,
921 int cborder, int win_gravity ) {
923 *fwidth = cwidth + FRAME_BORDER_WIDTH * 2;
924 *fheight = cheight + FRAME_BORDER_WIDTH + FRAME_TITLE_HEIGHT;
926 switch( win_gravity ) {
927 case XCB_GRAVITY_NORTH_WEST:
928 case XCB_GRAVITY_WEST:
929 case XCB_GRAVITY_SOUTH_WEST:
930 default:
931 *fx = cx;
932 break;
934 case XCB_GRAVITY_NORTH:
935 case XCB_GRAVITY_CENTER:
936 case XCB_GRAVITY_SOUTH:
937 case XCB_GRAVITY_STATIC:
938 *fx = cx + cborder - FRAME_BORDER_WIDTH - FRAME_X_BORDER;
939 break;
941 case XCB_GRAVITY_NORTH_EAST:
942 case XCB_GRAVITY_EAST:
943 case XCB_GRAVITY_SOUTH_EAST:
944 *fx = cx + ( ( cborder - FRAME_BORDER_WIDTH - FRAME_X_BORDER ) << 1 );
945 break;
948 switch( win_gravity ) {
949 case XCB_GRAVITY_NORTH_WEST:
950 case XCB_GRAVITY_NORTH:
951 case XCB_GRAVITY_NORTH_EAST:
952 default:
953 *fy = cy;
954 break;
956 case XCB_GRAVITY_WEST:
957 case XCB_GRAVITY_CENTER:
958 case XCB_GRAVITY_EAST:
959 *fy = cy + cborder - ( ( FRAME_BORDER_WIDTH +
960 FRAME_TITLE_HEIGHT ) >> 1 ) - FRAME_X_BORDER;
961 break;
963 case XCB_GRAVITY_SOUTH_WEST:
964 case XCB_GRAVITY_SOUTH:
965 case XCB_GRAVITY_SOUTH_EAST:
966 *fy = cy + ( cborder << 1 ) - FRAME_TITLE_HEIGHT - FRAME_BORDER_WIDTH -
967 ( FRAME_X_BORDER << 1 );
968 break;
970 case XCB_GRAVITY_STATIC:
971 *fy = cy + cborder - FRAME_TITLE_HEIGHT - FRAME_X_BORDER;
972 break;
976 extern void translate_frame_to_child( int *cx, int *cy, int fx, int fy,
977 int cborder, int win_gravity ) {
979 switch( win_gravity ) {
980 case XCB_GRAVITY_NORTH_WEST:
981 case XCB_GRAVITY_WEST:
982 case XCB_GRAVITY_SOUTH_WEST:
983 default:
984 *cx = fx;
985 break;
987 case XCB_GRAVITY_NORTH:
988 case XCB_GRAVITY_CENTER:
989 case XCB_GRAVITY_SOUTH:
990 case XCB_GRAVITY_STATIC:
991 *cx = fx - cborder + FRAME_BORDER_WIDTH + FRAME_X_BORDER;
992 break;
994 case XCB_GRAVITY_NORTH_EAST:
995 case XCB_GRAVITY_EAST:
996 case XCB_GRAVITY_SOUTH_EAST:
997 *cx = fx + ( ( FRAME_BORDER_WIDTH + FRAME_X_BORDER - cborder ) << 1 );
998 break;
1001 switch( win_gravity ) {
1002 case XCB_GRAVITY_NORTH_WEST:
1003 case XCB_GRAVITY_NORTH:
1004 case XCB_GRAVITY_NORTH_EAST:
1005 default:
1006 *cy = fy;
1007 break;
1009 case XCB_GRAVITY_WEST:
1010 case XCB_GRAVITY_CENTER:
1011 case XCB_GRAVITY_EAST:
1012 *cy = fy - cborder + ( ( FRAME_BORDER_WIDTH +
1013 FRAME_TITLE_HEIGHT ) >> 1 ) + FRAME_X_BORDER;
1014 break;
1016 case XCB_GRAVITY_SOUTH_WEST:
1017 case XCB_GRAVITY_SOUTH:
1018 case XCB_GRAVITY_SOUTH_EAST:
1019 *cy = fy - ( cborder << 1 ) + FRAME_TITLE_HEIGHT + FRAME_BORDER_WIDTH +
1020 ( FRAME_X_BORDER << 1 );
1021 break;
1023 case XCB_GRAVITY_STATIC:
1024 *cy = fy - cborder + FRAME_TITLE_HEIGHT + FRAME_X_BORDER;
1025 break;
1029 extern void apply_size_constraints( struct gwm_window *window, int *width,
1030 int *height ) {
1032 int eff_base_width = window->u.managed.base_width ?
1033 window->u.managed.base_width : window->u.managed.min_width,
1034 eff_base_height = window->u.managed.base_height ?
1035 window->u.managed.base_height : window->u.managed.min_height;
1037 /* Apply the minimum and maximum constraints. These are already known
1038 to be compatible. */
1039 if( *width < window->u.managed.min_width )
1040 *width = window->u.managed.min_width;
1042 if( *height < window->u.managed.min_height )
1043 *height = window->u.managed.min_height;
1045 if( *width > window->u.managed.max_width )
1046 *width = window->u.managed.max_width;
1048 if( *height > window->u.managed.max_height )
1049 *height = window->u.managed.max_height;
1051 /* Now round down each dimension to an integer multiple of increments.
1052 Rounding down cannot violate the maximum constraint, and since
1053 eff_base_* >= min_*, it will not reduce below the minimum constraint. */
1054 *width -= ( *width - eff_base_width ) % window->u.managed.width_inc;
1055 *height -= ( *height - eff_base_height ) % window->u.managed.height_inc;
1057 if( window->u.managed.min_aspect_x * *height >
1058 window->u.managed.min_aspect_y * *width ) {
1059 /* Minimum aspect ratio violated. Attempt to either increase the
1060 width or decrease the height (whichever is a smaller change), but
1061 don't do either if it would go outside the min/max bounds.
1062 Both division operations are safe (min_aspect_y is always
1063 positive, and min_aspect_x must be positive if there is a
1064 violation). Note that an exact solution might not be possible
1065 (e.g. certain cases where the aspect ratio and increments are
1066 coprime). */
1067 int min_x, max_y;
1069 min_x = ( window->u.managed.min_aspect_x * *height +
1070 ( window->u.managed.min_aspect_y - 1 ) ) /
1071 window->u.managed.min_aspect_y + window->u.managed.width_inc - 1;
1072 min_x -= ( min_x - eff_base_width ) % window->u.managed.width_inc;
1074 max_y = window->u.managed.min_aspect_y * *width /
1075 window->u.managed.min_aspect_x;
1076 max_y -= ( max_y - eff_base_height ) % window->u.managed.height_inc;
1078 if( min_x - *width < *height - max_y ) {
1079 /* The width change is smaller: prefer it if possible. */
1080 if( min_x >= window->u.managed.min_width )
1081 *width = min_x;
1082 else if( max_y < window->u.managed.max_height )
1083 *height = max_y;
1084 } else {
1085 /* The height change is smaller: prefer it if possible. */
1086 if( max_y < window->u.managed.max_height )
1087 *height = max_y;
1088 else if( min_x >= window->u.managed.min_width )
1089 *width = min_x;
1093 if( window->u.managed.max_aspect_x * *height <
1094 window->u.managed.max_aspect_y * *width ) {
1095 /* Maximum aspect ratio violated. Much like the case above... */
1096 int min_y, max_x;
1098 min_y = ( window->u.managed.max_aspect_y * *width +
1099 ( window->u.managed.max_aspect_x - 1 ) ) /
1100 window->u.managed.max_aspect_x + window->u.managed.height_inc - 1;
1101 min_y -= ( min_y - eff_base_height ) % window->u.managed.height_inc;
1103 max_x = window->u.managed.max_aspect_x * *height /
1104 window->u.managed.max_aspect_y;
1105 max_x -= ( max_x - eff_base_width ) % window->u.managed.width_inc;
1107 if( min_y - *height < *width - max_x ) {
1108 /* The height change is smaller: prefer it if possible. */
1109 if( min_y >= window->u.managed.min_height )
1110 *height = min_y;
1111 else if( max_x < window->u.managed.max_width )
1112 *width = max_x;
1113 } else {
1114 /* The width change is smaller: prefer it if possible. */
1115 if( max_x < window->u.managed.max_width )
1116 *width = max_x;
1117 else if( min_y >= window->u.managed.min_height )
1118 *height = min_y;
1123 static void stop_listening( struct gwm_window *window ) {
1125 uint32_t value;
1127 /* Ignore Window errors on the child, because it might be destroyed
1128 before we are notified. */
1130 value = 0;
1131 handle_error_reply( xcb_change_window_attributes_checked(
1132 c, window->w, XCB_CW_EVENT_MASK, &value ),
1133 ERR_MASK_WINDOW );
1135 #if USE_SHAPE
1136 if( have_extension[ EXT_SHAPE ] )
1137 handle_error_reply( xcb_shape_select_input_checked( c, window->w,
1138 FALSE ),
1139 ERR_MASK_WINDOW );
1140 #endif
1143 static void set_managed_state( struct gwm_window *window, int state ) {
1145 uint32_t values[ 2 ];
1147 assert( window->type == WINDOW_MANAGED );
1149 /* Place a WM_STATE property on the client window (ICCCM 2.0, section
1150 4.1.3.1). */
1151 values[ 0 ] = window->u.managed.state = state;
1152 values[ 1 ] = XCB_NONE;
1153 xcb_change_property( c, XCB_PROP_MODE_REPLACE, window->w,
1154 atoms[ ATOM_WM_STATE ], atoms[ ATOM_WM_STATE ],
1155 32, 2, values );
1158 static void place_window( struct gwm_window *window, int *x, int *y,
1159 int width, int height ) {
1161 int sx = screens[ window->screen ]->width_in_pixels - width;
1162 int sy = screens[ window->screen ]->height_in_pixels - height;
1163 unsigned long long hash;
1165 if( sx <= 0 )
1166 *x = 0;
1167 else {
1168 hash = window->w * 0x9B4A36D1;
1169 hash ^= hash >> 32;
1170 *x = hash % sx;
1173 if( sy <= 0 )
1174 *y = 0;
1175 else {
1176 hash = window->w * 0x9B4A36D1;
1177 hash ^= hash >> 32;
1178 *y = hash % sy;
1182 static void start_managing_window( struct gwm_window *window,
1183 xcb_get_window_attributes_reply_t *attr,
1184 xcb_get_geometry_reply_t *geom,
1185 #if USE_SHAPE
1186 xcb_shape_query_extents_reply_t *shape,
1187 #endif
1188 xcb_get_property_reply_t *props[],
1189 int map_request ) {
1191 int i;
1192 uint32_t values[ 4 ];
1193 struct gwm_window *frame, *button;
1194 xcb_window_t w = window->w;
1196 frame = add_window( xcb_generate_id( c ) );
1197 button = add_window( xcb_generate_id( c ) );
1198 window->screen = lookup_window( geom->root )->screen;
1199 window->type = WINDOW_MANAGED;
1200 window->u.managed.frame = frame;
1201 window->u.managed.border_width = geom->border_width;
1202 window->u.managed.cmap = attr->colormap;
1203 window->u.managed.hints = 0;
1204 window->u.managed.name = NULL;
1205 window->u.managed.state = STATE_WITHDRAWN;
1206 frame->screen = window->screen;
1207 frame->type = WINDOW_FRAME;
1208 frame->u.frame.child = window;
1209 frame->u.frame.button = button;
1210 button->screen = window->screen;
1211 button->type = WINDOW_BUTTON;
1212 button->u.button.frame = frame;
1214 for( i = 0; i < NUM_PROPS; i++ )
1215 managed_property_change( window, i, props[ i ] );
1217 translate_child_to_frame( &frame->u.frame.x, &frame->u.frame.y,
1218 &frame->u.frame.width, &frame->u.frame.height,
1219 geom->x, geom->y, geom->width, geom->height,
1220 geom->border_width,
1221 window->u.managed.win_gravity );
1223 if( map_request && !( window->u.managed.hints & HINT_POSITION ) ) {
1224 int x, y;
1226 place_window( window, &x, &y,
1227 frame->u.frame.width, frame->u.frame.height );
1229 frame->u.frame.x = x;
1230 frame->u.frame.y = y;
1233 /* Don't create frames entirely off-screen; ensure that at least 8 pixels
1234 are within the root. */
1235 if( frame->u.frame.x > screens[ frame->screen ]->width_in_pixels - 8 &&
1236 frame->u.frame.x + frame->u.frame.width >
1237 screens[ frame->screen ]->width_in_pixels )
1238 frame->u.frame.x = screens[ frame->screen ]->width_in_pixels - 8;
1239 else if( frame->u.frame.x < 0 &&
1240 frame->u.frame.x + frame->u.frame.width < 8 )
1241 frame->u.frame.x = 8 - frame->u.frame.width;
1243 if( frame->u.frame.y > screens[ frame->screen ]->height_in_pixels - 8 &&
1244 frame->u.frame.y + frame->u.frame.height >
1245 screens[ frame->screen ]->height_in_pixels )
1246 frame->u.frame.y = screens[ frame->screen ]->height_in_pixels - 8;
1247 else if( frame->u.frame.y < 0 &&
1248 frame->u.frame.y + frame->u.frame.height < 8 )
1249 frame->u.frame.y = 8 - frame->u.frame.height;
1251 values[ 0 ] = gwm_screens[ frame->screen ].pixels[
1252 COL_FRAME_INACTIVE ]; /* background pixel */
1253 values[ 1 ] = gwm_screens[ frame->screen ].pixels[
1254 COL_BORDER ]; /* border pixel */
1255 values[ 2 ] = XCB_GRAVITY_NORTH_WEST; /* bit gravity */
1256 values[ 3 ] = TRUE; /* override redirect */
1257 values[ 4 ] = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
1258 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_POINTER_MOTION_HINT |
1259 XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_EXPOSURE |
1260 XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
1262 xcb_create_window( c, XCB_COPY_FROM_PARENT, frame->w, geom->root,
1263 frame->u.frame.x, frame->u.frame.y,
1264 frame->u.frame.width, frame->u.frame.height, 1,
1265 XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
1266 XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
1267 XCB_CW_BIT_GRAVITY | XCB_CW_OVERRIDE_REDIRECT |
1268 XCB_CW_EVENT_MASK, values );
1270 values[ 0 ] = gwm_screens[ frame->screen ].pixels[
1271 COL_BUTTON_INACTIVE ]; /* background pixel */
1272 values[ 1 ] = gwm_screens[ frame->screen ].pixels[
1273 COL_BORDER ]; /* border pixel */
1274 values[ 2 ] = TRUE; /* override redirect */
1275 values[ 3 ] = XCB_EVENT_MASK_EXPOSURE;
1276 xcb_create_window( c, XCB_COPY_FROM_PARENT, button->w, frame->w,
1277 2, 1, FRAME_BUTTON_SIZE, FRAME_BUTTON_SIZE, 1,
1278 XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
1279 XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
1280 XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, values );
1282 xcb_grab_button( c, FALSE, button->w, XCB_EVENT_MASK_BUTTON_PRESS |
1283 XCB_EVENT_MASK_BUTTON_RELEASE |
1284 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW,
1285 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE,
1286 XCB_NONE, XCB_BUTTON_INDEX_ANY, 0x8000 );
1288 xcb_map_window( c, button->w );
1290 xcb_change_save_set( c, XCB_SET_MODE_INSERT, w );
1292 if( !map_request ) {
1293 /* If the window was mapped already, then reparenting it will
1294 generate an UnmapNotify. To avoid race conditions in discarding
1295 this unwanted event, temporarily reduce our event mask. */
1296 xcb_grab_server( c );
1298 values[ 0 ] = XCB_EVENT_MASK_PROPERTY_CHANGE |
1299 XCB_EVENT_MASK_COLOR_MAP_CHANGE;
1300 xcb_change_window_attributes( c, w, XCB_CW_EVENT_MASK, values );
1303 xcb_reparent_window( c, w, frame->w, FRAME_BORDER_WIDTH,
1304 FRAME_TITLE_HEIGHT );
1305 if( !map_request ) {
1306 values[ 0 ] = XCB_EVENT_MASK_STRUCTURE_NOTIFY |
1307 XCB_EVENT_MASK_PROPERTY_CHANGE |
1308 XCB_EVENT_MASK_COLOR_MAP_CHANGE;
1309 xcb_change_window_attributes( c, w, XCB_CW_EVENT_MASK, values );
1311 xcb_ungrab_server( c );
1314 if( geom->border_width ) {
1315 values[ 0 ] = 0;
1316 xcb_configure_window( c, w, XCB_CONFIG_WINDOW_BORDER_WIDTH, values );
1319 #if USE_SHAPE
1320 if( have_extension[ EXT_SHAPE ] && shape && shape->bounding_shaped )
1321 match_managed_shape( window, TRUE );
1322 #endif
1324 set_managed_state( window, map_request &&
1325 ( window->u.managed.hints & HINT_ICONIC ) ?
1326 STATE_ICONIC : STATE_NORMAL );
1328 if( window->u.managed.state == STATE_NORMAL ) {
1329 if( map_request )
1330 xcb_map_window( c, w );
1332 xcb_map_window( c, frame->w );
1335 /* An ugly hack to get the button cursor properly set now that
1336 the button has been created. */
1337 managed_property_change( window, PROP_WM_PROTOCOLS,
1338 props[ PROP_WM_PROTOCOLS ] );
1341 struct new_window {
1342 struct gwm_window *window; /* type WINDOW_INCOMPLETE */
1343 int map_request;
1344 xcb_get_geometry_cookie_t geometry_cookie;
1345 #if USE_SHAPE
1346 xcb_shape_query_extents_cookie_t shape_cookie;
1347 #endif
1348 xcb_get_property_cookie_t prop_cookies[ NUM_PROPS ];
1351 static void handle_manage_window( unsigned int sequence, void *reply,
1352 xcb_generic_error_t *error,
1353 union callback_param cp ) {
1355 struct new_window *nw = cp.p;
1356 int i;
1357 xcb_get_window_attributes_reply_t *attr = reply;
1358 xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(
1359 c, nw->geometry_cookie, NULL );
1360 #if USE_SHAPE
1361 xcb_shape_query_extents_reply_t *shape = have_extension[ EXT_SHAPE ] ?
1362 xcb_shape_query_extents_reply( c, nw->shape_cookie, NULL ) : NULL;
1363 #endif
1364 xcb_get_property_reply_t *props[ NUM_PROPS ];
1365 int have_all_props;
1367 for( i = 0, have_all_props = TRUE; i < NUM_PROPS; i++ )
1368 if( !( props[ i ] = xcb_get_property_reply( c, nw->prop_cookies[ i ],
1369 NULL ) ) )
1370 have_all_props = FALSE;
1372 if( error ) {
1373 show_error( error );
1375 free( error );
1378 if( attr && geom && have_all_props &&
1379 attr->_class == XCB_WINDOW_CLASS_INPUT_OUTPUT &&
1380 ( nw->map_request || ( attr->map_state == XCB_MAP_STATE_VIEWABLE &&
1381 !attr->override_redirect ) ) )
1382 start_managing_window( nw->window, attr, geom,
1383 #if USE_SHAPE
1384 shape,
1385 #endif
1386 props, nw->map_request );
1387 else {
1388 stop_listening( nw->window );
1389 forget_window( nw->window );
1392 if( attr )
1393 free( attr );
1395 if( geom )
1396 free( geom );
1398 #if USE_SHAPE
1399 if( shape )
1400 free( shape );
1401 #endif
1403 for( i = 0; i < NUM_PROPS; i++ )
1404 if( props[ i ] )
1405 free( props[ i ] );
1407 free( nw );
1410 extern void manage_window( xcb_window_t w, int map_request ) {
1412 struct new_window *nw = xmalloc( sizeof *nw );
1413 uint32_t values[ 2 ];
1414 int i;
1415 union callback_param cp;
1416 unsigned int seq;
1418 if( !( nw->window = add_window( w ) ) ) {
1419 /* The window already exists. This can happen if a client attempts
1420 more than one MapWindow before we've handle the first one, and
1421 so we receive multiple MapRequests. */
1422 free( nw );
1423 return;
1426 /* Our request for notification of events concerning the client
1427 window must be atomic with our query of its state (otherwise
1428 there will exist some interval where we will be unaware of updates,
1429 or receive notifications for updates which were already present in
1430 the originally queried state). Unfortunately this means that we're
1431 forced to receive events before we even know whether we're interested
1432 in managing the window. If we subsequently discover that we
1433 didn't want the events after all, we will call stop_listening()
1434 (below), and any corresponding events for the unknown window
1435 will be silently ignored. */
1436 xcb_grab_server( c );
1438 nw->map_request = map_request;
1439 nw->geometry_cookie = xcb_get_geometry( c, w );
1440 #if USE_SHAPE
1441 if( have_extension[ EXT_SHAPE ] )
1442 nw->shape_cookie = xcb_shape_query_extents( c, w );
1443 #endif
1444 for( i = 0; i < NUM_PROPS; i++ )
1445 nw->prop_cookies[ i ] = xcb_get_property( c, FALSE, w, prop_atoms[ i ],
1446 prop_types[ i ], 0,
1447 PROP_SIZE );
1449 seq = xcb_get_window_attributes( c, w ).sequence;
1451 values[ 0 ] = XCB_GRAVITY_NORTH_WEST;
1452 values[ 1 ] = XCB_EVENT_MASK_STRUCTURE_NOTIFY |
1453 XCB_EVENT_MASK_PROPERTY_CHANGE |
1454 XCB_EVENT_MASK_COLOR_MAP_CHANGE;
1455 xcb_change_window_attributes( c, w, XCB_CW_WIN_GRAVITY | XCB_CW_EVENT_MASK,
1456 values );
1458 #if USE_SHAPE
1459 if( have_extension[ EXT_SHAPE ] )
1460 xcb_shape_select_input( c, w, TRUE );
1461 #endif
1463 xcb_ungrab_server( c );
1465 cp.p = nw;
1466 handle_async_reply( seq, handle_manage_window, cp );
1468 nw->window->type = WINDOW_INCOMPLETE;
1469 nw->window->u.incomplete.sequence = seq;
1472 extern void unmanage_window( struct gwm_window *window ) {
1474 xcb_window_t w = window->w;
1475 struct gwm_window *frame = window->u.managed.frame;
1476 uint32_t border_width = window->u.managed.border_width;
1477 int x, y;
1479 if( focus_frame == frame )
1480 focus_frame = NULL;
1482 stop_listening( window );
1484 /* Ignore Window errors on the child, because it might be destroyed
1485 before we are notified. */
1487 handle_error_reply( xcb_change_save_set_checked(
1488 c, XCB_SET_MODE_DELETE, w ), ERR_MASK_WINDOW );
1490 /* Reparent the window back to the root; delete its WM_STATE
1491 property (see ICCCM 4.1.4); and map it if it was iconic. */
1492 translate_frame_to_child( &x, &y, frame->u.frame.x, frame->u.frame.y,
1493 border_width, window->u.managed.win_gravity );
1494 handle_error_reply( xcb_reparent_window_checked(
1495 c, w, screens[ window->screen ]->root,
1496 x, y ), ERR_MASK_WINDOW );
1498 handle_error_reply( xcb_delete_property_checked(
1499 c, w, atoms[ ATOM_WM_STATE ] ), ERR_MASK_WINDOW );
1501 handle_error_reply( xcb_configure_window_checked(
1502 c, w, XCB_CONFIG_WINDOW_BORDER_WIDTH,
1503 &border_width ), ERR_MASK_WINDOW );
1505 if( window->u.managed.state == STATE_ICONIC )
1506 handle_error_reply( xcb_map_window_checked( c, w ), ERR_MASK_WINDOW );
1508 xcb_destroy_window( c, frame->w );
1510 if( window->u.managed.name )
1511 free( window->u.managed.name );
1513 forget_window( window );
1514 forget_window( frame->u.frame.button );
1515 forget_window( frame );
1518 extern void iconic_to_normal( struct gwm_window *window ) {
1520 assert( window->type == WINDOW_MANAGED );
1522 set_managed_state( window, STATE_NORMAL );
1523 xcb_map_window( c, window->w );
1524 xcb_map_window( c, window->u.managed.frame->w );
1527 extern void deactivate_focus_frame( void ) {
1529 uint32_t n;
1531 if( !focus_frame )
1532 return;
1534 n = gwm_screens[ focus_frame->screen ].pixels[ COL_FRAME_INACTIVE ];
1535 xcb_change_window_attributes( c, focus_frame->w, XCB_CW_BACK_PIXEL, &n );
1537 queue_window_update( focus_frame, 0, 0, focus_frame->u.frame.width,
1538 focus_frame->u.frame.height, FALSE );
1541 extern void generic_expose( struct gwm_window *window,
1542 xcb_expose_event_t *ev ) {
1544 queue_window_update( window, ev->x, ev->y, ev->width, ev->height, TRUE );
1547 xcb_window_t passive_grab; /* the window which has the pointer automatically
1548 grabbed via a button press, or XCB_NONE */
1550 static void handle_fake_change_property( unsigned int sequence, void *reply,
1551 xcb_generic_error_t *error,
1552 union callback_param cp ) {
1554 xcb_selection_notify_event_t *p = cp.p;
1556 assert( !reply );
1558 if( error ) {
1559 free( error );
1561 p->property = XCB_NONE;
1564 handle_error_reply( xcb_send_event_checked( c, FALSE, p->requestor, 0,
1565 (char *) p ), ERR_MASK_WINDOW );
1567 free( p );
1570 static void fake_selection_request( struct gwm_window *window,
1571 xcb_selection_request_event_t *ev ) {
1573 if( ev->time != XCB_CURRENT_TIME && ev->time < window->u.fake.timestamp &&
1574 ev->target == atoms[ ATOM_VERSION ] ) {
1575 /* ICCCM version identification: ICCCM 2.0, section 4.3. */
1576 static const uint32_t values[ 2 ] = { 2, 0 };
1577 struct xcb_selection_notify_event_t *p = xmalloc( sizeof *p );
1578 union callback_param cp;
1580 p->requestor = ev->requestor;
1581 p->selection = ev->selection;
1582 p->target = ev->target;
1583 p->property = ev->property;
1584 p->time = ev->time;
1586 handle_async_reply( xcb_change_property_checked(
1587 c, XCB_PROP_MODE_REPLACE, ev->requestor,
1588 atoms[ ATOM_VERSION ], INTEGER, 32, 2,
1589 values ).sequence, handle_fake_change_property,
1590 cp );
1591 } else {
1592 struct xcb_selection_notify_event_t not;
1594 not.requestor = ev->requestor;
1595 not.selection = ev->selection;
1596 not.target = ev->target;
1597 not.property = XCB_NONE;
1598 not.time = ev->time;
1600 handle_error_reply( xcb_send_event_checked( c, FALSE, not.requestor,
1601 0, (char *) &not ),
1602 ERR_MASK_WINDOW );
1606 static event_handler fake_handlers[] = {
1607 NULL, /* Error */
1608 NULL, /* Reply */
1609 NULL, /* KeyPress */
1610 NULL, /* KeyRelease */
1611 NULL, /* ButtonPress */
1612 NULL, /* ButtonRelease */
1613 NULL, /* MotionNotify */
1614 NULL, /* EnterNotify */
1615 NULL, /* LeaveNotify */
1616 NULL, /* FocusIn */
1617 NULL, /* FocusOut */
1618 NULL, /* KeymapNotify */
1619 NULL, /* Expose */
1620 NULL, /* GraphicsExpose */
1621 NULL, /* NoExposure */
1622 NULL, /* VisibilityNotify */
1623 NULL, /* CreateNotify */
1624 NULL, /* DestroyNotify */
1625 NULL, /* UnmapNotify */
1626 NULL, /* MapNotify */
1627 NULL, /* MapRequest */
1628 NULL, /* ReparentNotify */
1629 NULL, /* ConfigureNotify */
1630 NULL, /* ConfigureRequest */
1631 NULL, /* GravityNotify */
1632 NULL, /* ResizeRequest */
1633 NULL, /* CirculateNotify */
1634 NULL, /* CirculateRequest */
1635 NULL, /* PropertyNotify */
1636 NULL, /* SelectionClear */
1637 (event_handler) fake_selection_request, /* SelectionRequest */
1638 NULL, /* SelectionNotify */
1639 NULL, /* ColormapNotify */
1640 NULL, /* ClientMessage */
1641 NULL, /* MappingNotify */
1642 NULL, /* (synthetic) */
1643 NULL /* ShapeNotify */
1646 static event_handler feedback_handlers[] = {
1647 NULL, /* Error */
1648 NULL, /* Reply */
1649 NULL, /* KeyPress */
1650 NULL, /* KeyRelease */
1651 NULL, /* ButtonPress */
1652 NULL, /* ButtonRelease */
1653 NULL, /* MotionNotify */
1654 NULL, /* EnterNotify */
1655 NULL, /* LeaveNotify */
1656 NULL, /* FocusIn */
1657 NULL, /* FocusOut */
1658 NULL, /* KeymapNotify */
1659 (event_handler) generic_expose,
1660 NULL, /* GraphicsExpose */
1661 NULL, /* NoExposure */
1662 NULL, /* VisibilityNotify */
1663 NULL, /* CreateNotify */
1664 NULL, /* DestroyNotify */
1665 NULL, /* UnmapNotify */
1666 NULL, /* MapNotify */
1667 NULL, /* MapRequest */
1668 NULL, /* ReparentNotify */
1669 NULL, /* ConfigureNotify */
1670 NULL, /* ConfigureRequest */
1671 NULL, /* GravityNotify */
1672 NULL, /* ResizeRequest */
1673 NULL, /* CirculateNotify */
1674 NULL, /* CirculateRequest */
1675 NULL, /* PropertyNotify */
1676 NULL, /* SelectionClear */
1677 NULL, /* SelectionRequest */
1678 NULL, /* SelectionNotify */
1679 NULL, /* ColormapNotify */
1680 NULL, /* ClientMessage */
1681 NULL, /* MappingNotify */
1682 NULL /* (synthetic) */
1685 static const event_handler *handlers[] = {
1686 root_handlers, managed_handlers, frame_handlers, button_handlers,
1687 fake_handlers, feedback_handlers, NULL
1690 static void setup_display( void ) {
1692 int i;
1693 xcb_intern_atom_cookie_t atom_cookies[ NUM_ATOMS ];
1694 const xcb_setup_t *setup;
1695 xcb_screen_iterator_t iter;
1696 xcb_intern_atom_cookie_t *screen_atom_cookies;
1697 uint32_t n;
1698 xcb_get_selection_owner_cookie_t *screen_owner_cookies;
1699 xcb_window_t *screen_owners;
1700 xcb_client_message_event_t msg;
1701 xcb_void_cookie_t *cookies;
1702 xcb_query_tree_cookie_t *tree_cookies;
1704 table_init( &windows );
1705 table_init( &update_windows );
1707 c = xcb_connect( NULL, NULL );
1709 if( xcb_connection_has_error( c ) )
1710 fatal( "could not connect to server" );
1712 for( i = 0; i < NUM_EXTENSIONS; i++ )
1713 xcb_prefetch_extension_data( c, (xcb_extension_t *) extensions[ i ] );
1715 for( i = 0; i < NUM_ATOMS; i++ )
1716 atom_cookies[ i ] = xcb_intern_atom( c, 0, strlen( atom_names[ i ] ),
1717 atom_names[ i ] );
1719 setup = xcb_get_setup( c );
1721 iter = xcb_setup_roots_iterator( setup );
1722 num_screens = iter.rem;
1723 screens = xmalloc( num_screens * sizeof *screens );
1724 gwm_screens = xmalloc( num_screens * sizeof *gwm_screens );
1725 screen_atom_cookies = alloca( num_screens * sizeof *screen_atom_cookies );
1726 screen_owners = alloca( num_screens * sizeof *screen_owners );
1727 screen_owner_cookies = alloca( num_screens * sizeof *screen_owner_cookies );
1728 cookies = alloca( num_screens * sizeof *cookies );
1729 tree_cookies = alloca( num_screens * sizeof *tree_cookies );
1731 for( i = 0; iter.rem; i++, xcb_screen_next( &iter ) ) {
1732 static char wm_atom_str[ 16 ] = "WM_S";
1733 xcb_depth_iterator_t depth_iter;
1735 screens[ i ] = iter.data;
1736 screen_atom_cookies[ i ] =
1737 xcb_intern_atom( c, 0, 4 + sprintf( wm_atom_str + 4, "%d", i ),
1738 wm_atom_str );
1740 for( depth_iter = xcb_screen_allowed_depths_iterator( screens[ i ] );
1741 depth_iter.rem; xcb_depth_next( &depth_iter ) ) {
1742 xcb_visualtype_iterator_t visual_iter;
1744 for( visual_iter = xcb_depth_visuals_iterator( depth_iter.data );
1745 visual_iter.rem; xcb_visualtype_next( &visual_iter ) ) {
1746 xcb_visualtype_t *visual = visual_iter.data;
1748 if( screens[ i ]->root_visual == visual->visual_id ) {
1749 gwm_screens[ i ].root_visual = visual;
1750 goto visual_found;
1754 fatal( "Root visual for screen %d (0x%X) not found", i,
1755 screens[ i ]->root_visual );
1756 visual_found:
1760 fake_window = add_window( xcb_generate_id( c ) );
1761 fake_window->screen = 0;
1762 fake_window->type = WINDOW_FAKE;
1764 n = XCB_EVENT_MASK_PROPERTY_CHANGE;
1765 xcb_create_window( c, 0, fake_window->w, screens[ 0 ]->root, 0, 0, 1, 1, 0,
1766 XCB_WINDOW_CLASS_INPUT_ONLY, screens[ 0 ]->root_visual,
1767 XCB_CW_EVENT_MASK, &n );
1769 /* Obtain a timestamp we can use for our selection acquiry time
1770 (ICCCM 2.0, section 2.1). */
1771 xcb_change_property( c, XCB_PROP_MODE_APPEND, fake_window->w, WM_HINTS,
1772 WM_HINTS, 32, 0, NULL );
1774 xcb_flush( c ); /* BLOCK */
1776 for( i = 0; i < NUM_EXTENSIONS; i++ ) {
1777 const xcb_query_extension_reply_t *r;
1779 if( ( have_extension[ i ] =
1780 ( r = xcb_get_extension_data(
1781 c, (xcb_extension_t *) extensions[ i ] ) ) && r->present ) ) {
1782 extension_event[ i ] = r->first_event;
1783 extension_error[ i ] = r->first_error;
1787 decorate_core_init();
1788 update_window = core_update_window;
1790 for( i = 0; i < NUM_ATOMS; i++ ) {
1791 xcb_intern_atom_reply_t *r =
1792 xcb_intern_atom_reply( c, atom_cookies[ i ], NULL );
1794 atoms[ i ] = r->atom;
1795 free( r );
1798 prop_atoms[ PROP_WM_COLORMAP_WINDOWS ] = atoms[ ATOM_WM_COLORMAP_WINDOWS ];
1799 prop_atoms[ PROP_WM_HINTS ] = WM_HINTS;
1800 prop_atoms[ PROP_WM_NAME ] = WM_NAME;
1801 prop_atoms[ PROP_WM_NORMAL_HINTS ] = WM_NORMAL_HINTS;
1802 prop_atoms[ PROP_WM_PROTOCOLS ] = atoms[ ATOM_WM_PROTOCOLS ];
1804 prop_types[ PROP_WM_COLORMAP_WINDOWS ] = WINDOW;
1805 prop_types[ PROP_WM_HINTS ] = WM_HINTS;
1806 prop_types[ PROP_WM_NAME ] = XCB_GET_PROPERTY_TYPE_ANY;
1807 prop_types[ PROP_WM_NORMAL_HINTS ] = WM_SIZE_HINTS;
1808 prop_types[ PROP_WM_PROTOCOLS ] = ATOM;
1810 for( i = 0; i < num_screens; i++ ) {
1811 xcb_intern_atom_reply_t *r =
1812 xcb_intern_atom_reply( c, screen_atom_cookies[ i ], NULL );
1814 gwm_screens[ i ].wm_atom = r->atom;
1815 free( r );
1818 /* Listen for a selection timestamp. */
1819 for(;;) {
1820 /* BLOCK */
1821 xcb_generic_event_t *ev = wait_for_event();
1823 if( !ev )
1824 fatal( "did not receive property notification" );
1826 if( ev->response_type & SEND_EVENT_MASK ) {
1827 free( ev );
1829 continue; /* ignore synthetic events */
1830 } else if( ev->response_type == XCB_PROPERTY_NOTIFY ) {
1831 xcb_property_notify_event_t *not =
1832 (xcb_property_notify_event_t *) ev;
1834 if( not->window != fake_window->w ||
1835 not->atom != WM_HINTS ||
1836 not->state != XCB_PROPERTY_NEW_VALUE ) {
1837 warning( "unexpected property notification" );
1839 continue;
1842 fake_window->u.fake.timestamp = not->time;
1844 free( ev );
1846 break;
1847 } else if( ev->response_type != XCB_MAPPING_NOTIFY ) {
1848 /* Ignore MappingNotify -- we'll query the mappings later. */
1849 warning( "unexpected event while waiting for property "
1850 "notification" );
1852 #if DEBUG
1853 show_event( ev );
1854 #endif
1856 free( ev );
1860 n = 0;
1861 xcb_change_window_attributes( c, fake_window->w, XCB_CW_EVENT_MASK, &n );
1863 do {
1864 int existing_manager;
1865 int managers_changed;
1866 sigset_t sigs, oldsigs;
1868 for( i = 0; i < num_screens; i++ )
1869 screen_owner_cookies[ i ] =
1870 xcb_get_selection_owner( c, gwm_screens[ i ].wm_atom );
1872 xcb_flush( c ); /* BLOCK */
1874 existing_manager = 0;
1875 for( i = 0; i < num_screens; i++ ) {
1876 xcb_get_selection_owner_reply_t *r =
1877 xcb_get_selection_owner_reply( c, screen_owner_cookies[ i ],
1878 NULL );
1880 if( ( screen_owners[ i ] = r->owner ) != XCB_NONE ) {
1881 if( !flag_replace )
1882 printf( "Screen %d is already managed.\n", i );
1884 existing_manager++;
1887 free( r );
1890 if( existing_manager && !flag_replace ) {
1891 printf( "Use \"%s --replace\" to replace any existing "
1892 "window manager(s).\n", argv0 );
1894 exit( 1 );
1897 if( existing_manager ) {
1898 uint32_t n;
1900 /* Wait for existing manager(s) to terminate (ICCCM 2.0,
1901 section 2.8). */
1902 n = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
1903 for( i = 0; i < num_screens; i++ )
1904 if( screen_owners[ i ] != XCB_NONE )
1905 xcb_change_window_attributes( c, screen_owners[ i ],
1906 XCB_CW_EVENT_MASK, &n );
1908 for( i = 0; i < num_screens; i++ )
1909 screen_owner_cookies[ i ] =
1910 xcb_get_selection_owner( c, gwm_screens[ i ].wm_atom );
1912 xcb_flush( c ); /* BLOCK */
1914 managers_changed = FALSE;
1915 for( i = 0; i < num_screens; i++ ) {
1916 xcb_get_selection_owner_reply_t *r =
1917 xcb_get_selection_owner_reply( c, screen_owner_cookies[ i ],
1918 NULL );
1920 if( r->owner != screen_owners[ i ] )
1921 managers_changed = TRUE;
1923 free( r );
1926 if( managers_changed ) {
1927 /* Argh. A window manager changed during all our bookkeeping.
1928 Undo our notifications, and start again. */
1929 n = 0;
1930 for( i = 0; i < num_screens; i++ )
1931 if( screen_owners[ i ] != XCB_NONE )
1932 xcb_change_window_attributes( c, screen_owners[ i ],
1933 XCB_CW_EVENT_MASK, &n );
1935 continue;
1939 /* Acquire ownership of WM_Sn selections (ICCCM 2.0, section 4.3). */
1940 for( i = 0; i < num_screens; i++ ) {
1941 xcb_set_selection_owner( c, fake_window->w,
1942 gwm_screens[ i ].wm_atom,
1943 fake_window->u.fake.timestamp );
1944 screen_owner_cookies[ i ] =
1945 xcb_get_selection_owner( c, gwm_screens[ i ].wm_atom );
1948 xcb_flush( c ); /* BLOCK */
1949 sync_with_callback( screen_owner_cookies[ num_screens - 1 ].sequence );
1951 for( i = 0; i < num_screens; i++ ) {
1952 xcb_get_selection_owner_reply_t *r =
1953 xcb_get_selection_owner_reply( c, screen_owner_cookies[ i ],
1954 NULL );
1956 if( r->owner != fake_window->w )
1957 fatal( "did not acquire window manager selection" );
1959 free( r );
1962 alarm( 3 );
1964 /* Listen for DestroyNotify on any selection holders. */
1965 while( existing_manager ) {
1966 static int killed_existing_managers;
1968 xcb_generic_event_t *ev = wait_for_event(); /* BLOCK */
1970 if( !ev ) {
1971 if( flag_force && signal_caught == SIGALRM &&
1972 !killed_existing_managers ) {
1973 signal_caught = 0;
1975 for( i = 0; i < num_screens; i++ )
1976 if( screen_owners[ i ] )
1977 xcb_kill_client( c, screen_owners[ i ] );
1979 killed_existing_managers = TRUE;
1981 alarm( 3 );
1983 continue;
1986 fatal( "did not receive destroy notification" );
1989 if( ev->response_type & SEND_EVENT_MASK ) {
1990 free( ev );
1992 continue; /* ignore synthetic events */
1993 } else if( ev->response_type == XCB_DESTROY_NOTIFY ) {
1994 xcb_destroy_notify_event_t *not =
1995 (xcb_destroy_notify_event_t *) ev;
1997 for( i = 0; i < num_screens; i++ )
1998 if( not->window == screen_owners[ i ] ) {
1999 screen_owners[ i ] = XCB_NONE;
2000 existing_manager--;
2003 free( ev );
2004 } else if( ev->response_type == XCB_SELECTION_CLEAR )
2005 /* We lost a window manager selection before we even
2006 started. Just shut down quietly. */
2007 exit( 0 );
2008 else if( ev->response_type == XCB_MAPPING_NOTIFY )
2009 /* Ignore MappingNotify. */
2010 free( ev );
2011 else if( !ev->response_type ) {
2012 /* Error. */
2013 xcb_generic_error_t *error = (xcb_generic_error_t *) ev;
2015 /* Expect Window and Value errors, because old window managers
2016 might die before we sent a corresponding request. */
2017 if( error->error_code != XCB_WINDOW &&
2018 error->error_code != XCB_VALUE )
2019 show_error( error );
2021 free( ev );
2022 } else {
2023 warning( "unexpected event while waiting for destroy "
2024 "notification" );
2026 #if DEBUG
2027 show_event( ev );
2028 #endif
2030 free( ev );
2034 sigfillset( &sigs );
2035 sigprocmask( SIG_BLOCK, &sigs, &oldsigs );
2037 alarm( 0 );
2038 if( signal_caught == SIGALRM )
2039 signal_caught = 0;
2041 sigprocmask( SIG_SETMASK, &oldsigs, NULL );
2042 } while( 0 );
2044 msg.response_type = XCB_CLIENT_MESSAGE;
2045 msg.format = 32;
2046 msg.sequence = 0;
2047 msg.type = atoms[ ATOM_MANAGER ];
2048 msg.data.data32[ 0 ] = fake_window->u.fake.timestamp;
2049 /* Data32[ 1 ] varies; will be completed later. */
2050 msg.data.data32[ 2 ] = fake_window->w;
2051 msg.data.data32[ 3 ] = 0;
2052 msg.data.data32[ 4 ] = 0;
2054 n = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
2055 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
2057 for( i = 0; i < num_screens; i++ ) {
2058 /* Send StructureNotify ClientMessage (ICCCM 2.0, section 2.8). */
2059 msg.window = screens[ i ]->root;
2060 msg.data.data32[ 1 ] = gwm_screens[ i ].wm_atom;
2061 xcb_send_event( c, FALSE, screens[ i ]->root,
2062 XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *) &msg );
2064 cookies[ i ] =
2065 xcb_change_window_attributes_checked( c, screens[ i ]->root,
2066 XCB_CW_EVENT_MASK, &n );
2068 tree_cookies[ i ] = xcb_query_tree( c, screens[ i ]->root );
2071 xcb_set_input_focus( c, XCB_INPUT_FOCUS_NONE, XCB_INPUT_FOCUS_POINTER_ROOT,
2072 fake_window->u.fake.timestamp );
2074 get_keyboard_mapping( setup->min_keycode,
2075 setup->max_keycode - setup->min_keycode + 1 );
2076 get_modifier_mapping();
2078 xcb_flush( c ); /* BLOCK */
2079 sync_with_callback( cookies[ num_screens - 1 ].sequence );
2081 for( i = 0; i < num_screens; i++ ) {
2082 xcb_generic_error_t *error = xcb_request_check( c, cookies[ i ] );
2083 xcb_query_tree_reply_t *r;
2084 struct gwm_window *window;
2085 xcb_window_t *children;
2086 int j;
2088 if( error )
2089 fatal( "could not acquire SubstructureRedirect" );
2091 window = add_window( screens[ i ]->root );
2092 window->screen = i;
2093 window->type = WINDOW_ROOT;
2095 gwm_screens[ i ].cmap = XCB_NONE;
2096 gwm_screens[ i ].cmap_time = fake_window->u.fake.timestamp;
2098 r = xcb_query_tree_reply( c, tree_cookies[ i ], NULL );
2099 children = xcb_query_tree_children( r );
2100 for( j = 0; j < r->children_len; j++ )
2101 manage_window( children[ j ], FALSE );
2103 free( r );
2107 static void handle_mapping_notify( xcb_mapping_notify_event_t *ev ) {
2109 switch( ev->request ) {
2110 case XCB_MAPPING_MODIFIER:
2111 get_modifier_mapping();
2112 break;
2114 case XCB_MAPPING_KEYBOARD:
2115 get_keyboard_mapping( ev->first_keycode, ev->count );
2116 break;
2118 case XCB_MAPPING_POINTER:
2119 break;
2123 static void handle_client_message( xcb_client_message_event_t *ev ) {
2125 if( ev->type == atoms[ ATOM_WM_CHANGE_STATE ] ) {
2126 struct gwm_window *window;
2128 if( ( window = lookup_window( ev->window ) ) &&
2129 window->type == WINDOW_MANAGED &&
2130 window->u.managed.state == STATE_NORMAL &&
2131 ev->data.data32[ 0 ] == STATE_ICONIC ) {
2132 uint32_t n;
2134 set_managed_state( window, STATE_ICONIC );
2135 /* We need to unmap our child without invoking the normal
2136 UnmapNotify handler response. Unfortunately it's very
2137 difficult to communicate to the handler the distinction
2138 between the resultant UnmapNotify from an unmap here
2139 and an unrelated event. Checking the request sequence
2140 is not robust, because asynchronous events might not be
2141 assigned unique sequence numbers. Instead, we grab
2142 the server and temporarily change our event mask, which
2143 seems a heavyweight approach but does guarantee that
2144 only this event will be ignored. */
2145 xcb_grab_server( c );
2147 n = XCB_EVENT_MASK_PROPERTY_CHANGE |
2148 XCB_EVENT_MASK_COLOR_MAP_CHANGE;
2149 xcb_change_window_attributes( c, window->w, XCB_CW_EVENT_MASK, &n );
2151 xcb_unmap_window( c, window->w );
2153 n = XCB_EVENT_MASK_STRUCTURE_NOTIFY |
2154 XCB_EVENT_MASK_PROPERTY_CHANGE |
2155 XCB_EVENT_MASK_COLOR_MAP_CHANGE;
2156 xcb_change_window_attributes( c, window->w, XCB_CW_EVENT_MASK, &n );
2158 xcb_ungrab_server( c );
2160 xcb_unmap_window( c, window->u.managed.frame->w );
2162 } else if( ev->type == atoms[ ATOM_WM_COLORMAP_NOTIFY ] ) {
2163 struct gwm_window *window;
2165 if( ( window = lookup_window( ev->window ) ) &&
2166 window->type == WINDOW_ROOT &&
2167 !ev->data.data32[ 1 ] )
2168 /* Ignore the case where the client is starting installation:
2169 if it is well-behaved, it will do so only when the pointer
2170 is grabbed (in which case we won't do anything to conflict
2171 with it); if it is not well-behaved, it's not worth trying
2172 to accomodate it. */
2173 install_window_colormap( window->screen, focus_frame ?
2174 focus_frame->u.frame.child : NULL,
2175 ev->data.data32[ 0 ] );
2179 static void handle_events( void ) {
2181 xcb_generic_event_t *ev;
2183 while( ( ev = wait_for_event() ) ) {
2184 int event_type = ev->response_type;
2185 int event_base_type = event_type & 0x7F;
2186 xcb_window_t w;
2188 /* Determine event window and time. */
2189 switch( event_base_type ) {
2190 case 0: /* Error */
2191 case 1: /* Reply */
2192 case XCB_KEYMAP_NOTIFY:
2193 case XCB_CLIENT_MESSAGE:
2194 case XCB_MAPPING_NOTIFY:
2195 w = XCB_NONE;
2196 break;
2198 case XCB_KEY_PRESS:
2199 w = ( (xcb_key_press_event_t *) ev )->event;
2200 latest_timestamp = ( (xcb_key_press_event_t *) ev )->time;
2201 break;
2203 case XCB_KEY_RELEASE:
2204 w = ( (xcb_key_release_event_t *) ev )->event;
2205 latest_timestamp = ( (xcb_key_release_event_t *) ev )->time;
2206 break;
2208 case XCB_BUTTON_PRESS:
2209 w = ( (xcb_button_press_event_t *) ev )->event;
2210 latest_timestamp = ( (xcb_button_press_event_t *) ev )->time;
2211 break;
2213 case XCB_BUTTON_RELEASE:
2214 w = ( (xcb_button_release_event_t *) ev )->event;
2215 latest_timestamp = ( (xcb_button_release_event_t *) ev )->time;
2216 break;
2218 case XCB_MOTION_NOTIFY:
2219 w = ( (xcb_motion_notify_event_t *) ev )->event;
2220 latest_timestamp = ( (xcb_motion_notify_event_t *) ev )->time;
2221 break;
2223 case XCB_ENTER_NOTIFY:
2224 case XCB_LEAVE_NOTIFY:
2225 w = ( (xcb_enter_notify_event_t *) ev )->event;
2226 latest_timestamp = ( (xcb_enter_notify_event_t *) ev )->time;
2227 break;
2229 case XCB_FOCUS_IN:
2230 case XCB_FOCUS_OUT:
2231 w = ( (xcb_focus_in_event_t *) ev )->event;
2232 break;
2234 case XCB_EXPOSE:
2235 w = ( (xcb_expose_event_t *) ev )->window;
2236 break;
2238 case XCB_GRAPHICS_EXPOSURE:
2239 w = ( (xcb_graphics_exposure_event_t *) ev )->drawable;
2240 break;
2242 case XCB_NO_EXPOSURE:
2243 w = ( (xcb_no_exposure_event_t *) ev )->drawable;
2244 break;
2246 case XCB_VISIBILITY_NOTIFY:
2247 w = ( (xcb_visibility_notify_event_t *) ev )->window;
2248 break;
2250 case XCB_CREATE_NOTIFY:
2251 w = ( (xcb_create_notify_event_t *) ev )->parent;
2252 break;
2254 case XCB_DESTROY_NOTIFY:
2255 w = ( (xcb_destroy_notify_event_t *) ev )->event;
2256 break;
2258 case XCB_UNMAP_NOTIFY:
2259 w = ( (xcb_unmap_notify_event_t *) ev )->event;
2260 break;
2262 case XCB_MAP_NOTIFY:
2263 w = ( (xcb_map_notify_event_t *) ev )->event;
2264 break;
2266 case XCB_MAP_REQUEST:
2267 w = ( (xcb_map_request_event_t *) ev )->parent;
2268 break;
2270 case XCB_REPARENT_NOTIFY:
2271 w = ( (xcb_reparent_notify_event_t *) ev )->event;
2272 break;
2274 case XCB_CONFIGURE_NOTIFY:
2275 w = ( (xcb_configure_notify_event_t *) ev )->event;
2276 break;
2278 case XCB_CONFIGURE_REQUEST:
2279 w = ( (xcb_configure_request_event_t *) ev )->parent;
2280 break;
2282 case XCB_GRAVITY_NOTIFY:
2283 w = ( (xcb_gravity_notify_event_t *) ev )->event;
2284 break;
2286 case XCB_RESIZE_REQUEST:
2287 w = ( (xcb_resize_request_event_t *) ev )->window;
2288 break;
2290 case XCB_CIRCULATE_NOTIFY:
2291 case XCB_CIRCULATE_REQUEST:
2292 w = ( (xcb_circulate_notify_event_t *) ev )->event;
2293 break;
2295 case XCB_PROPERTY_NOTIFY:
2296 w = ( (xcb_property_notify_event_t *) ev )->window;
2297 latest_timestamp = ( (xcb_property_notify_event_t *) ev )->time;
2298 break;
2300 case XCB_SELECTION_CLEAR:
2301 w = ( (xcb_selection_clear_event_t *) ev )->owner;
2302 latest_timestamp = ( (xcb_selection_clear_event_t *) ev )->time;
2303 break;
2305 case XCB_SELECTION_REQUEST:
2306 w = ( (xcb_selection_request_event_t *) ev )->owner;
2307 if( ( (xcb_selection_request_event_t *) ev )->time )
2308 latest_timestamp =
2309 ( (xcb_selection_request_event_t *) ev )->time;
2310 break;
2312 case XCB_SELECTION_NOTIFY:
2313 w = ( (xcb_selection_notify_event_t *) ev )->requestor;
2314 if( ( (xcb_selection_notify_event_t *) ev )->time )
2315 latest_timestamp =
2316 ( (xcb_selection_notify_event_t *) ev )->time;
2317 break;
2319 case XCB_COLORMAP_NOTIFY:
2320 w = ( (xcb_colormap_notify_event_t *) ev )->window;
2321 break;
2323 default:
2324 #if USE_SHAPE
2325 if( event_base_type == extension_event[ EXT_SHAPE ] ) {
2326 w = ( (xcb_shape_notify_event_t *) ev )->affected_window;
2327 event_type = SHAPE_NOTIFY;
2328 break;
2330 #endif
2331 warning( "Unknown event" );
2332 break;
2335 if( ev->response_type & 0x80 )
2336 event_type = SYNTHETIC_EVENT;
2338 /* Global handling for events (before specific window handler). */
2339 switch( event_type ) {
2340 case 0: /* Error */
2341 show_error( (xcb_generic_error_t *) ev );
2342 break;
2344 case 1: /* Reply */
2345 warning( "handle_events: received reply message" );
2346 break;
2348 case XCB_BUTTON_PRESS:
2349 if( initial_press( (xcb_button_press_event_t *) ev )
2350 && !passive_grab )
2351 passive_grab = w;
2352 break;
2354 case XCB_KEYMAP_NOTIFY:
2355 break;
2357 case XCB_SELECTION_CLEAR:
2358 /* We've lost the manager selection (see ICCCM 2.0, section
2359 2.8); close down gracefully. */
2360 free( ev );
2361 return;
2363 case XCB_MAPPING_NOTIFY:
2364 handle_mapping_notify( (xcb_mapping_notify_event_t *) ev );
2365 break;
2367 case SYNTHETIC_EVENT:
2368 if( ev->response_type == ( XCB_CLIENT_MESSAGE | SEND_EVENT_MASK ) )
2369 handle_client_message( (xcb_client_message_event_t *) ev );
2370 break;
2373 if( w != XCB_NONE ) {
2374 struct gwm_window *window;
2375 void ( *handler )( struct gwm_window *, xcb_generic_event_t * );
2377 if( ( window = lookup_window( w ) ) &&
2378 handlers[ window->type ] &&
2379 ( handler = handlers[ window->type ][ event_type ] ) )
2380 handler( window, ev );
2383 /* Global handling for events (after specific window handler). */
2384 switch( event_type ) {
2385 case XCB_BUTTON_RELEASE:
2386 if( final_release( (xcb_button_release_event_t *) ev ) )
2387 passive_grab = XCB_NONE;
2388 break;
2391 free( ev );
2395 static void handle_intern_atom( unsigned int sequence, void *reply,
2396 xcb_generic_error_t *error,
2397 union callback_param p ) {
2399 if( error )
2400 free( error );
2402 if( reply )
2403 free( reply );
2406 static void shutdown_display( void ) {
2408 int i;
2409 uint32_t n;
2410 xcb_intern_atom_cookie_t cookie;
2411 union callback_param cp;
2413 retry:
2414 for( i = 0; i < windows.used; i++ )
2415 if( windows.values[ i ]->type == WINDOW_MANAGED ) {
2416 unmanage_window( windows.values[ i ] );
2418 goto retry;
2421 /* The ICCCM says that using CurrentTime for SetInputFocus is naughty
2422 (section 4.2.7). But we don't have much alternative in this case:
2423 if we're shutting down in response to a signal, for instance. */
2424 xcb_set_input_focus( c, XCB_INPUT_FOCUS_NONE, XCB_INPUT_FOCUS_POINTER_ROOT,
2425 XCB_CURRENT_TIME );
2427 decorate_core_done();
2429 n = 0;
2430 for( i = 0; i < num_screens; i++ )
2431 xcb_change_window_attributes( c, screens[ i ]->root,
2432 XCB_CW_EVENT_MASK, &n );
2434 /* A pointless request to generate a reply, so we'll know when we've
2435 processed everything the server will send us. */
2436 cookie = xcb_intern_atom( c, TRUE, 4, "ATOM" );
2437 cp.l = 0;
2438 handle_async_reply( cookie.sequence, handle_intern_atom, cp );
2440 xcb_flush( c );
2442 sync_with_callback( cookie.sequence );
2444 xcb_disconnect( c );
2445 #if DEBUG
2446 /* Be pedantic about deallocating memory, to assist any tools that are
2447 looking for leaks. */
2448 forget_window( fake_window );
2450 retry_root:
2451 for( i = 0; i < windows.used; i++ )
2452 if( windows.values[ i ]->type == WINDOW_ROOT ) {
2453 forget_window( windows.values[ i ] );
2455 goto retry_root;
2458 /* All windows have now been deallocated -- release the tables, too. */
2459 table_destroy( &windows );
2460 table_destroy( &update_windows );
2462 cleanup_keyboard();
2464 free( screens );
2465 free( gwm_screens );
2467 cleanup_utf8();
2469 muntrace();
2470 #endif
2473 static void unknown( char *opt ) {
2475 printf( "%s: unknown option %s\n"
2476 "Usage: %s [OPTION]...\n"
2477 "Try \"%s --help\" for more information.\n",
2478 argv0, opt, argv0, argv0 );
2481 extern int main( int argc, char *argv[] ) {
2483 int i, j;
2484 struct sigaction sa;
2485 int which_signal;
2486 static int flag_help, flag_version;
2487 static const struct option {
2488 const char *opt;
2489 int *flag;
2490 } options[] = {
2491 #if DEBUG
2492 { "debug", &flag_debug },
2493 #endif
2494 { "force", &flag_force },
2495 { "help", &flag_help },
2496 { "replace", &flag_replace },
2497 { "version", &flag_version }
2499 #define NUM_OPTIONS ( sizeof options / sizeof *options )
2501 #if DEBUG && HAVE_MTRACE
2502 mtrace();
2503 #endif
2505 argv0 = argv[ 0 ];
2507 for( i = 1; i < argc; i++ )
2508 if( argv[ i ][ 0 ] == '-' ) {
2509 if( argv[ i ][ 1 ] == '-' && argv[ i ][ 2 ] ) {
2510 for( j = 0; j < NUM_OPTIONS; j++ )
2511 if( !strncmp( argv[ i ] + 2, options[ j ].opt,
2512 strlen( argv[ i ] + 2 ) ) ) {
2513 *options[ j ].flag = 1;
2514 break;
2517 if( j == NUM_OPTIONS ) {
2518 unknown( argv[ i ] );
2520 return 1;
2522 } else if( argv[ i ][ 1 ] != '-' ) {
2523 for( j = 0; j < NUM_OPTIONS; j++ )
2524 if( argv[ i ][ 1 ] == options[ j ].opt[ 0 ] &&
2525 !argv[ i ][ 2 ] ) {
2526 *options[ j ].flag = 1;
2527 break;
2530 if( j == NUM_OPTIONS ) {
2531 unknown( argv[ i ] );
2533 return 1;
2536 } else {
2537 unknown( argv[ i ] );
2539 return 1;
2542 if( flag_help ) {
2543 printf( "Usage: %s [OPTION]...\n"
2544 "Options:\n"
2545 #if DEBUG
2546 " -d, --debug Show all events received\n"
2547 #endif
2548 " -f, --force If replacing another window manager, "
2549 "terminate it if necessary\n"
2550 " -h, --help Display this information and exit\n"
2551 " -r, --replace Take over window management if another "
2552 "manager exists\n"
2553 " -v, --version Display version information and exit\n"
2554 "Please send bug reports to <" PACKAGE_BUGREPORT ">.\n",
2555 argv0 );
2557 return 0;
2560 if( flag_version ) {
2561 puts( PACKAGE_STRING "\nPlease send bug reports to <"
2562 PACKAGE_BUGREPORT ">." );
2564 return 0;
2567 sa.sa_handler = catch_signal;
2568 sigemptyset( &sa.sa_mask );
2569 sa.sa_flags = 0;
2570 sigaction( SIGHUP, &sa, NULL );
2571 sigaction( SIGINT, &sa, NULL );
2572 sigaction( SIGTERM, &sa, NULL );
2574 sa.sa_handler = alarm_signal;
2575 sigaction( SIGALRM, &sa, NULL );
2577 sa.sa_handler = child_signal;
2578 sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
2579 sigaction( SIGCHLD, &sa, NULL );
2581 setup_display();
2583 handle_events();
2584 which_signal = signal_caught;
2585 signal_caught = 0; /* acknowledge receipt, so we get normal event
2586 processing during shutdown */
2588 if( xcb_connection_has_error( c ) )
2589 /* Don't bother attempting orderly shutdown. */
2590 fatal( "server connection error" );
2592 shutdown_display();
2594 if( which_signal ) {
2595 raise( which_signal );
2597 return 1;
2600 return 0;