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/>.
45 #include <xcb/composite.h>
48 #include <xcb/damage.h>
51 #include <xcb/render.h>
54 #include <xcb/shape.h>
57 #include <xcb/xcbext.h>
59 #include <xcb/xfixes.h>
65 #include "decorate-core.h"
67 #include "decorate-render.h"
74 #include "window-table.h"
77 xcb_timestamp_t latest_timestamp
;
79 xcb_atom_t atoms
[ NUM_ATOMS
];
83 _NET_NUMBER_OF_DESKTOPS (1),
84 _NET_DESKTOP_GEOMETRY,
85 _NET_DESKTOP_VIEWPORT (0,0),
86 _NET_CURRENT_DESKTOP (0),
88 _NET_SUPPORTING_WM_CHECK properties to root windows. */
89 static const char *atom_names
[ NUM_ATOMS
] = {
97 "WM_COLORMAP_WINDOWS",
104 xcb_atom_t prop_atoms
[ NUM_PROPS
];
105 xcb_atom_t prop_types
[ NUM_PROPS
];
107 static const xcb_extension_t
*extensions
[ EXTENSIONS_SIZE
] = {
125 int have_extension
[ EXTENSIONS_SIZE
];
126 uint8_t extension_event
[ EXTENSIONS_SIZE
],
127 extension_error
[ EXTENSIONS_SIZE
];
130 xcb_screen_t
**screens
;
131 struct gwm_screen
*gwm_screens
;
134 xcb_render_pictformat_t fmt_rgba8
, fmt_a8
;
135 xcb_render_directformat_t dfmt_rgba8
, dfmt_a8
;
138 xcb_cursor_t cursors
[ NUM_CURSORS
];
140 struct gwm_window
*fake_window
, *focus_frame
;
143 static int flag_replace
, flag_force
;
145 static int flag_debug
;
148 static volatile int signal_caught
;
150 static void ( *update_window
)( struct gwm_window
*window
);
152 extern FORMAT( printf
, 1, 2 ) void warning( char *format
, ... ) {
156 va_start( val
, format
);
158 fprintf( stderr
, "%s: warning: ", argv0
);
159 vfprintf( stderr
, format
, val
);
160 putc( '\n', stderr
);
165 extern FORMAT( printf
, 1, 2 ) NORETURN
void fatal( char *format
, ... ) {
169 va_start( val
, format
);
171 fprintf( stderr
, "%s: ", argv0
);
172 vfprintf( stderr
, format
, val
);
173 putc( '\n', stderr
);
177 exit( 1 ); /* don't attempt an orderly shutdown */
180 extern MALLOC
void *xmalloc( size_t size
) {
184 /* It would be nice to clean up gracefully on allocation errors.
185 Unfortunately, doing so would require transmitting many requests
186 to the X server, which would require further allocations which
187 are likely to fail. Simply aborting with an error message is
188 therefore probably about as effective, and much simpler. */
190 if( !( p
= malloc( size
) ) ) {
199 extern void *xrealloc( void *p
, size_t size
) {
201 if( !( p
= realloc( p
, size
) ) ) {
210 extern MALLOC
void *xcalloc( size_t number
, size_t size
) {
214 if( !( p
= calloc( number
, size
) ) ) {
223 static void catch_signal( int n
) {
229 sa
.sa_handler
= SIG_DFL
;
230 sigemptyset( &sa
.sa_mask
);
232 sigaction( SIGHUP
, &sa
, NULL
);
233 sigaction( SIGINT
, &sa
, NULL
);
234 sigaction( SIGTERM
, &sa
, NULL
);
237 static void alarm_signal( int n
) {
242 static void child_signal( int n
) {
244 while( waitpid( -1, 0, WNOHANG
) > 0 )
248 static struct async_callback
{
249 unsigned int sequence
;
250 void ( *callback
)( unsigned int sequence
, void *reply
,
251 xcb_generic_error_t
*error
, union callback_param p
);
252 union callback_param p
;
253 struct async_callback
*next
;
254 } *queue_head
, *queue_tail
;
256 static void check_async_callbacks( void ) {
259 xcb_generic_error_t
*error
;
261 while( queue_head
&& xcb_poll_for_reply( c
, queue_head
->sequence
,
263 struct async_callback
*entry
= queue_head
;
265 if( !( queue_head
= entry
->next
) )
268 entry
->callback( entry
->sequence
, reply
, error
, entry
->p
);
274 extern void sync_with_callback( unsigned int sequence
) {
276 while( queue_head
&& sequence
- queue_head
->sequence
< UINT_MAX
>> 1 ) {
277 struct async_callback
*entry
= queue_head
;
278 xcb_generic_error_t
*error
;
279 void *reply
= xcb_wait_for_reply( c
, entry
->sequence
, &error
);
281 if( !( queue_head
= entry
->next
) )
284 entry
->callback( entry
->sequence
, reply
, error
, entry
->p
);
291 static void show_window( char *label
, xcb_window_t w
) {
293 /* We don't want to use lookup_window here, since mere debugging
294 output shouldn't trigger the side effect of finishing incomplete
296 struct gwm_window
*window
= table_lookup( &windows
, w
);
298 printf( " %s %08X (", label
, w
);
301 fputs( "None", stdout
);
303 struct gwm_window
*client
;
304 switch( window
->type
) {
306 printf( "root, screen %d", window
->screen
);
315 fputs( "frame, ", stdout
);
316 client
= window
->u
.frame
.child
;
320 fputs( "button, ", stdout
);
321 client
= window
->u
.button
.frame
->u
.frame
.child
;
325 fputs( "fake", stdout
);
329 case WINDOW_FEEDBACK
:
330 printf( "feedback, screen %d", window
->screen
);
334 case WINDOW_INCOMPLETE
:
335 fputs( "incomplete", stdout
);
339 case WINDOW_CHILDLESS
:
340 fputs( "childless", stdout
);
346 fputs( client
->u
.managed
.name
? client
->u
.managed
.name
: "unnamed",
349 fputs( "unknown", stdout
);
355 static void show_atom( char *label
, xcb_atom_t atom
) {
357 static const char *predefined_names
[ 69 ] = {
358 "None", "PRIMARY", "SECONDARY", "ARC", "ATOM", "BITMAP", "CARDINAL",
359 "COLORMAP", "CURSOR", "CUT_BUFFER0", "CUT_BUFFER1", "CUT_BUFFER2",
360 "CUT_BUFFER3", "CUT_BUFFER4", "CUT_BUFFER5", "CUT_BUFFER6",
361 "CUT_BUFFER7", "DRAWABLE", "FONT", "INTEGER", "PIXMAP", "POINT",
362 "RECTANGLE", "RESOURCE_MANAGER", "RGB_COLOR_MAP", "RGB_BEST_MAP",
363 "RGB_BLUE_MAP", "RGB_DEFAULT_MAP", "RGB_GRAY_MAP", "RGB_GREEN_MAP",
364 "RGB_RED_MAP", "STRING", "VISUALID", "WINDOW", "WM_COMMAND",
365 "WM_HINTS", "WM_CLIENT_MACHINE", "WM_ICON_NAME", "WM_ICON_SIZE",
366 "WM_NAME", "WM_NORMAL_HINTS", "WM_SIZE_HINTS", "WM_ZOOM_HINTS",
367 "MIN_SPACE", "NORM_SPACE", "MAX_SPACE", "END_SPACE", "SUPERSCRIPT_X",
368 "SUPERSCRIPT_Y", "SUBSCRIPT_X", "SUBSCRIPT_Y", "UNDERLINE_POSITION",
369 "UNDERLINE_THICKNESS", "STRIKEOUT_ASCENT", "STRIKEOUT_DESCENT",
370 "ITALIC_ANGLE", "X_HEIGHT", "QUAD_WIDTH", "WEIGHT", "POINT_SIZE",
371 "RESOLUTION", "COPYRIGHT", "NOTICE", "FONT_NAME", "FAMILY_NAME",
372 "FULL_NAME", "CAP_HEIGHT", "WM_CLASS", "WM_TRANSIENT_FOR"
377 name
= predefined_names
[ atom
];
381 for( i
= 0; i
< NUM_ATOMS
; i
++ )
382 if( atoms
[ i
] == atom
) {
383 name
= atom_names
[ i
];
391 printf( " %s %d (%s)\n", label
, atom
, name
);
395 extern void show_error( xcb_generic_error_t
*error
) {
397 xcb_value_error_t
*verror
= (xcb_value_error_t
*) error
;
399 static const char *type_strings
[ XCB_IMPLEMENTATION
+ 1 ] = {
400 "Unknown", "Request", "Value", "Window", "Pixmap", "Atom", "Cursor",
401 "Font", "Match", "Drawable", "Access", "Alloc", "Colormap", "GContext",
402 "IDChoice", "Name", "Length", "Implementation"
404 const char *type
, *bad_value_str
;
405 char num
[ 32 ], bad_num
[ 32 ];
407 if( error
->error_code
<= XCB_IMPLEMENTATION
)
408 type
= type_strings
[ error
->error_code
];
410 sprintf( num
, "%d", error
->error_code
);
414 switch( error
->error_code
) {
425 sprintf( bad_num
, ", val %X", verror
->bad_value
);
426 bad_value_str
= bad_num
;
435 if( have_extension
[ EXT_RENDER
] &&
436 error
->error_code
>= extension_error
[ EXT_RENDER
] &&
437 error
->error_code
<= extension_error
[ EXT_RENDER
] +
439 static const char *render_strings
[ XCB_RENDER_GLYPH
+ 1 ] = {
440 "PictFormat", "Picture", "PictOp", "GlyphSet", "Glyph"
443 type
= render_strings
[ error
->error_code
-
444 extension_error
[ EXT_RENDER
] ];
448 warning( "unexpected error (type %s, sequence %d; opcode %d %d%s)", type
,
449 error
->sequence
, verror
->major_opcode
, verror
->minor_opcode
,
452 warning( "unexpected error (type %d, sequence %d; opcode %d %d)",
453 error
->error_code
, error
->sequence
,
454 verror
->major_opcode
, verror
->minor_opcode
);
459 static void show_event( xcb_generic_event_t
*generic
) {
461 static const char *event_names
[ XCB_MAPPING_NOTIFY
+ 1 ] = {
462 "Error", "Reply", "KeyPress", "KeyRelease", "ButtonPress",
463 "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify",
464 "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExposure",
465 "NoExposure", "VisibilityNotify", "CreateNotify", "DestroyNotify",
466 "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify",
467 "ConfigureNotify", "ConfigureRequest", "GravityNotify",
468 "ResizeRequest", "CirculateNotify", "CirculateRequest",
469 "PropertyNotify", "SelectionClear", "SelectionRequest",
470 "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify"
473 xcb_key_press_event_t kbm
; /* KeyPress, KeyRelease, ButtonPress,
474 ButtonRelease, MotionNotify */
475 xcb_enter_notify_event_t el
; /* EnterNotify, LeaveNotify */
476 xcb_focus_in_event_t f
; /* FocusIn, FocusOut */
477 xcb_keymap_notify_event_t km
; /* KeymapNotify */
478 xcb_expose_event_t e
; /* Expose */
479 xcb_graphics_exposure_event_t g
; /* GraphicsExposure */
480 xcb_no_exposure_event_t n
; /* NoExposure */
481 xcb_visibility_notify_event_t v
; /* VisibilityNotify */
482 xcb_create_notify_event_t c
; /* CreateNotify */
483 xcb_destroy_notify_event_t d
; /* DestroyNotify */
484 xcb_unmap_notify_event_t u
; /* UnmapNotify */
485 xcb_map_notify_event_t m
; /* MapNotify */
486 xcb_map_request_event_t mr
; /* MapRequest */
487 xcb_reparent_notify_event_t r
; /* ReparentNotify */
488 xcb_configure_notify_event_t con
; /* ConfigureNotify */
489 xcb_configure_request_event_t cr
; /* ConfigureRequest */
490 xcb_gravity_notify_event_t gn
; /* GravityNotify */
491 xcb_resize_request_event_t rr
; /* ResizeRequest */
492 xcb_circulate_notify_event_t cir
; /* CirculateNotify */
493 xcb_circulate_request_event_t cirr
; /* CirculateRequest */
494 xcb_property_notify_event_t p
; /* PropertyNotify */
495 xcb_selection_clear_event_t sc
; /* SelectionClear */
496 xcb_selection_request_event_t sr
; /* SelectionRequest */
497 xcb_selection_notify_event_t sn
; /* SelectionNotify */
498 xcb_colormap_notify_event_t cm
; /* ColormapNotify */
499 xcb_client_message_event_t cli
; /* ClientMessage */
500 xcb_mapping_notify_event_t map
; /* MappingNotify */
501 } *ev
= (union event
*) generic
;
502 int type
= generic
->response_type
& 0x7F;
505 /* FIXME handle extensions */
507 if( !( generic
->response_type
& ~SEND_EVENT_MASK
) ) {
508 show_error( (xcb_generic_error_t
*) generic
);
513 printf( "Event %d (%s%s)\n", generic
->response_type
,
514 type
<= XCB_MAPPING_NOTIFY
? event_names
[ type
] : "Unknown",
515 generic
->response_type
& SEND_EVENT_MASK
? ", synthetic" : "" );
517 if( type
< XCB_KEY_PRESS
|| type
> XCB_MAPPING_NOTIFY
)
520 if( type
!= XCB_KEYMAP_NOTIFY
)
521 printf( " Sequence %X\n", generic
->full_sequence
);
525 case XCB_KEY_RELEASE
:
526 case XCB_BUTTON_PRESS
:
527 case XCB_BUTTON_RELEASE
:
528 case XCB_MOTION_NOTIFY
:
529 show_window( "Root", ev
->kbm
.root
);
530 show_window( "Event", ev
->kbm
.event
);
531 show_window( "Child", ev
->kbm
.child
);
532 printf( " Same-screen %s\n"
534 " Event x/y %d, %d\n"
538 ev
->kbm
.same_screen
? "Yes" : "No",
539 ev
->kbm
.root_x
, ev
->kbm
.root_y
,
540 ev
->kbm
.event_x
, ev
->kbm
.event_y
,
541 ev
->kbm
.detail
, ev
->kbm
.state
, ev
->kbm
.time
);
544 case XCB_ENTER_NOTIFY
:
545 case XCB_LEAVE_NOTIFY
:
546 show_window( "Root", ev
->el
.root
);
547 show_window( "Event", ev
->el
.event
);
548 show_window( "Child", ev
->el
.child
);
549 printf( " Same-screen %s\n"
551 " Event x/y %d, %d\n"
557 ev
->el
.same_screen_focus
& 2 ? "Yes" : "No",
558 ev
->el
.root_x
, ev
->el
.root_y
,
559 ev
->el
.event_x
, ev
->el
.event_y
,
560 ev
->el
.mode
, ev
->el
.detail
, ev
->el
.same_screen_focus
& 1,
561 ev
->el
.state
, ev
->el
.time
);
566 show_window( "Event", ev
->f
.event
);
569 ev
->f
.mode
, ev
->f
.detail
);
571 case XCB_KEYMAP_NOTIFY
:
572 for( i
= 0; i
< 0xF8; i
++ )
573 if( ev
->km
.keys
[ i
>> 3 ] & ( 1 << ( i
& 7 ) ) )
574 printf( " key %d down\n", i
);
579 show_window( "Window", ev
->e
.window
);
580 printf( " x/y %d, %d\n"
581 " width/height %d x %d\n"
583 ev
->e
.x
, ev
->e
.y
, ev
->e
.width
, ev
->e
.height
, ev
->e
.count
);
586 case XCB_GRAPHICS_EXPOSURE
:
587 show_window( "Drawable", ev
->g
.drawable
);
588 printf( " x/y %d, %d\n"
589 " width/height %d x %d\n"
592 ev
->g
.x
, ev
->g
.y
, ev
->g
.width
, ev
->g
.height
, ev
->g
.count
,
593 ev
->g
.major_opcode
, ev
->g
.minor_opcode
);
596 case XCB_NO_EXPOSURE
:
597 show_window( "Drawable", ev
->n
.drawable
);
598 printf( " Opcode %d, %d\n",
599 ev
->n
.major_opcode
, ev
->n
.minor_opcode
);
602 case XCB_VISIBILITY_NOTIFY
:
603 show_window( "Window", ev
->v
.window
);
604 printf( " State %d\n", ev
->v
.state
);
607 case XCB_CREATE_NOTIFY
:
608 show_window( "Parent", ev
->c
.parent
);
609 show_window( "Window", ev
->c
.window
);
610 printf( " x/y %d, %d\n"
611 " width/height %d x %d\n"
613 " Override redirect %s\n",
615 ev
->c
.width
, ev
->c
.height
,
617 ev
->c
.override_redirect
? "Yes" : "No" );
620 case XCB_DESTROY_NOTIFY
:
621 show_window( "Event", ev
->d
.event
);
622 show_window( "Window", ev
->d
.window
);
625 case XCB_UNMAP_NOTIFY
:
626 show_window( "Event", ev
->u
.event
);
627 show_window( "Window", ev
->u
.window
);
628 printf( " From configure: %s\n",
629 ev
->u
.from_configure
? "Yes" : "No" );
633 show_window( "Event", ev
->m
.event
);
634 show_window( "Window", ev
->m
.window
);
635 printf( " Override redirect: %s\n",
636 ev
->m
.override_redirect
? "Yes" : "No" );
639 case XCB_MAP_REQUEST
:
640 show_window( "Parent", ev
->mr
.parent
);
641 show_window( "Window", ev
->mr
.window
);
644 case XCB_REPARENT_NOTIFY
:
645 show_window( "Event", ev
->r
.event
);
646 show_window( "Window", ev
->r
.window
);
647 show_window( "Parent", ev
->r
.parent
);
648 printf( " x/y %d, %d\n"
649 " Override redirect %s\n",
651 ev
->r
.override_redirect
? "Yes" : "No" );
654 case XCB_CONFIGURE_NOTIFY
:
655 show_window( "Event", ev
->con
.event
);
656 show_window( "Window", ev
->con
.window
);
657 printf( " x/y %d, %d\n"
658 " width/height %d x %d\n"
659 " Border width %d\n",
660 ev
->con
.x
, ev
->con
.y
,
661 ev
->con
.width
, ev
->con
.height
,
662 ev
->con
.border_width
);
663 show_window( "Above sibling", ev
->con
.above_sibling
);
664 printf( " Override redirect %s\n",
665 ev
->con
.override_redirect
? "Yes" : "No" );
668 case XCB_CONFIGURE_REQUEST
:
669 show_window( "Parent", ev
->cr
.parent
);
670 show_window( "Window", ev
->cr
.window
);
671 printf( " x/y %d, %d\n"
672 " width/height %d x %d\n"
673 " Border width %d\n",
675 ev
->cr
.width
, ev
->cr
.height
,
676 ev
->cr
.border_width
);
677 show_window( "Sibling", ev
->cr
.sibling
);
678 printf( " Stack mode %d\n"
679 " Value mask %04X\n",
680 ev
->cr
.stack_mode
, ev
->cr
.value_mask
);
683 case XCB_GRAVITY_NOTIFY
:
684 show_window( "Event", ev
->gn
.event
);
685 show_window( "Window", ev
->gn
.window
);
686 printf( " x/y %d, %d\n",
687 ev
->gn
.x
, ev
->gn
.y
);
690 case XCB_RESIZE_REQUEST
:
691 show_window( "Window", ev
->rr
.window
);
692 printf( " width/height %d x %d\n",
693 ev
->rr
.width
, ev
->rr
.height
);
696 case XCB_CIRCULATE_NOTIFY
:
697 show_window( "Event", ev
->cir
.event
);
698 show_window( "Window", ev
->cir
.window
);
699 printf( " Place %d\n", ev
->cir
.place
);
702 case XCB_CIRCULATE_REQUEST
:
703 show_window( "Parent", ev
->cirr
.event
);
704 show_window( "Window", ev
->cirr
.window
);
705 printf( " Place %d\n", ev
->cirr
.place
);
708 case XCB_PROPERTY_NOTIFY
:
709 show_window( "Window", ev
->p
.window
);
710 show_atom( "Atom", ev
->p
.atom
);
711 printf( " State %d\n"
713 ev
->p
.state
, ev
->p
.time
);
716 case XCB_SELECTION_CLEAR
:
717 show_window( "Owner", ev
->sc
.owner
);
718 show_atom( "Selection", ev
->sc
.selection
);
719 printf( " Time %d\n", ev
->sc
.time
);
722 case XCB_SELECTION_REQUEST
:
723 show_window( "Owner", ev
->sr
.owner
);
724 show_atom( "Selection", ev
->sr
.selection
);
725 show_atom( "Target", ev
->sr
.target
);
726 show_atom( "Property", ev
->sr
.property
);
727 show_window( "Requestor", ev
->sr
.requestor
);
728 printf( " Time %d\n", ev
->sr
.time
);
731 case XCB_SELECTION_NOTIFY
:
732 show_window( "Requestor", ev
->sn
.requestor
);
733 show_atom( "Selection", ev
->sn
.selection
);
734 show_atom( "Target", ev
->sn
.target
);
735 show_atom( "Property", ev
->sn
.property
);
736 printf( " Time %d\n", ev
->sn
.time
);
739 case XCB_COLORMAP_NOTIFY
:
740 show_window( "Window", ev
->cm
.window
);
741 printf( " Colormap %d\n"
745 ev
->cm
._new
? "Yes" : "No",
749 case XCB_CLIENT_MESSAGE
:
750 show_window( "Window", ev
->cli
.window
);
751 show_atom( "Type", ev
->cli
.type
);
752 printf( " Format %d\n", ev
->cli
.format
);
754 switch( ev
->cli
.format
) {
756 for( i
= 0; i
< 20; i
++ )
757 printf( " %2d %02X\n", i
, ev
->cli
.data
.data8
[ i
] );
761 for( i
= 0; i
< 10; i
++ )
762 printf( " %d %04X\n", i
, ev
->cli
.data
.data16
[ i
] );
766 for( i
= 0; i
< 5; i
++ )
767 printf( " %d %08X\n", i
, ev
->cli
.data
.data32
[ i
] );
773 case XCB_MAPPING_NOTIFY
:
774 printf( " Request %d\n", ev
->map
.request
);
775 if( ev
->map
.request
== XCB_MAPPING_KEYBOARD
)
776 printf( " First keycode %d\n"
778 ev
->map
.first_keycode
, ev
->map
.count
);
784 extern xcb_generic_event_t
*wait_for_event( void ) {
786 xcb_generic_event_t
*ev
;
787 sigset_t sigs
, oldsigs
;
788 int fd
= xcb_get_file_descriptor( c
);
796 if( xcb_connection_has_error( c
) )
799 ev
= xcb_poll_for_event( c
);
802 if( flag_debug
&& ev
)
806 check_async_callbacks(); /* must poll for events first, since
807 polling replies won't dequeue them */
812 if( update_windows
.used
) {
813 struct gwm_window
*update
= update_windows
.values
[ 0 ];
815 update_window( update
);
816 window_update_done( update
);
823 sigemptyset( &sigs
);
824 sigaddset( &sigs
, SIGHUP
);
825 sigaddset( &sigs
, SIGINT
);
826 sigaddset( &sigs
, SIGTERM
);
828 sigprocmask( SIG_BLOCK
, &sigs
, &oldsigs
);
830 if( signal_caught
) {
831 sigprocmask( SIG_SETMASK
, &oldsigs
, NULL
);
840 if( ppoll( &pfd
, 1, NULL
, &oldsigs
) < 0 && errno
!= EINTR
)
846 if( pselect( fd
+ 1, &fds
, NULL
, NULL
, NULL
, &oldsigs
) < 0 &&
851 sigprocmask( SIG_SETMASK
, &oldsigs
, NULL
);
855 extern void handle_async_reply( unsigned int sequence
,
856 void ( *callback
)( unsigned int sequence
,
858 xcb_generic_error_t
*error
,
859 union callback_param p
),
860 union callback_param p
) {
862 struct async_callback
*entry
= xmalloc( sizeof *entry
);
864 entry
->sequence
= sequence
;
865 entry
->callback
= callback
;
870 queue_tail
= queue_tail
->next
= entry
;
872 queue_head
= queue_tail
= entry
;
875 static void error_callback( unsigned int sequence
, void *reply
,
876 xcb_generic_error_t
*error
,
877 union callback_param p
) {
879 unsigned ignore
= p
.l
;
885 return; /* no error */
887 if( !( ignore
& ( 1 << ( error
->error_code
- 1 ) ) ) )
888 show_error( error
); /* an error we didn't expect */
893 extern void handle_error_reply( xcb_void_cookie_t cookie
, unsigned ignore
) {
895 union callback_param p
;
899 handle_async_reply( cookie
.sequence
, error_callback
, p
);
902 static void install_colormap( int screen
, xcb_colormap_t cmap
,
903 xcb_timestamp_t t
) {
905 if( (int) t
- (int) gwm_screens
[ screen
].cmap_time
> 0 ) {
906 gwm_screens
[ screen
].cmap_time
= t
;
908 if( gwm_screens
[ screen
].cmap
!= cmap
)
909 xcb_install_colormap( c
, gwm_screens
[ screen
].cmap
= cmap
);
913 struct cmap_callback
{
918 static void handle_get_window_attributes( unsigned int sequence
, void *reply
,
919 xcb_generic_error_t
*error
,
920 union callback_param cp
) {
922 struct cmap_callback
*cc
= cp
.p
;
925 /* Ignore Window errors, since we were given the ID by a client
926 and we don't trust them to get it right. */
927 if( error
->error_code
!= XCB_WINDOW
)
934 xcb_get_window_attributes_reply_t
*r
= reply
;
936 install_colormap( cc
->screen
, r
->colormap
? r
->colormap
:
937 screens
[ cc
->screen
]->default_colormap
, cc
->t
);
945 /* Install a window's colormap on a screen, or the default colormap if
947 extern void install_window_colormap( int screen
, struct gwm_window
*window
,
948 xcb_timestamp_t t
) {
950 assert( !window
|| window
->type
== WINDOW_MANAGED
);
952 if( window
&& window
->u
.managed
.cmap_window
) {
953 union callback_param cp
;
954 struct cmap_callback
*cc
= xmalloc( sizeof *cc
);
959 handle_async_reply( xcb_get_window_attributes(
960 c
, window
->u
.managed
.cmap_window
).sequence
,
961 handle_get_window_attributes
, cp
);
963 install_colormap( screen
, window
&& window
->u
.managed
.cmap
?
964 window
->u
.managed
.cmap
:
965 screens
[ screen
]->default_colormap
, t
);
968 /* Return TRUE iff an event is the only button pressed (and so would
969 initiate a passive grab). */
970 extern CONST
int initial_press( xcb_button_press_event_t
*ev
) {
972 return !( ev
->state
& 0x1F00 );
975 /* Return TRUE iff an event is the release of the last button (and so would
976 terminate a passive grab). */
977 extern CONST
int final_release( xcb_button_release_event_t
*ev
) {
979 return ( ev
->state
& 0x1F00 ) == ( 0x80 << ev
->detail
);
982 extern void translate_child_to_frame( int *fx
, int *fy
, int *fwidth
,
983 int *fheight
, int cx
, int cy
,
984 int cwidth
, int cheight
,
985 int cborder
, int win_gravity
) {
987 *fwidth
= cwidth
+ FRAME_BORDER_WIDTH
* 2;
988 *fheight
= cheight
+ FRAME_BORDER_WIDTH
+ FRAME_TITLE_HEIGHT
;
990 switch( win_gravity
) {
991 case XCB_GRAVITY_NORTH_WEST
:
992 case XCB_GRAVITY_WEST
:
993 case XCB_GRAVITY_SOUTH_WEST
:
998 case XCB_GRAVITY_NORTH
:
999 case XCB_GRAVITY_CENTER
:
1000 case XCB_GRAVITY_SOUTH
:
1001 case XCB_GRAVITY_STATIC
:
1002 *fx
= cx
+ cborder
- FRAME_BORDER_WIDTH
- FRAME_X_BORDER
;
1005 case XCB_GRAVITY_NORTH_EAST
:
1006 case XCB_GRAVITY_EAST
:
1007 case XCB_GRAVITY_SOUTH_EAST
:
1008 *fx
= cx
+ ( ( cborder
- FRAME_BORDER_WIDTH
- FRAME_X_BORDER
) << 1 );
1012 switch( win_gravity
) {
1013 case XCB_GRAVITY_NORTH_WEST
:
1014 case XCB_GRAVITY_NORTH
:
1015 case XCB_GRAVITY_NORTH_EAST
:
1020 case XCB_GRAVITY_WEST
:
1021 case XCB_GRAVITY_CENTER
:
1022 case XCB_GRAVITY_EAST
:
1023 *fy
= cy
+ cborder
- ( ( FRAME_BORDER_WIDTH
+
1024 FRAME_TITLE_HEIGHT
) >> 1 ) - FRAME_X_BORDER
;
1027 case XCB_GRAVITY_SOUTH_WEST
:
1028 case XCB_GRAVITY_SOUTH
:
1029 case XCB_GRAVITY_SOUTH_EAST
:
1030 *fy
= cy
+ ( cborder
<< 1 ) - FRAME_TITLE_HEIGHT
- FRAME_BORDER_WIDTH
-
1031 ( FRAME_X_BORDER
<< 1 );
1034 case XCB_GRAVITY_STATIC
:
1035 *fy
= cy
+ cborder
- FRAME_TITLE_HEIGHT
- FRAME_X_BORDER
;
1040 extern void translate_frame_to_child( int *cx
, int *cy
, int fx
, int fy
,
1041 int cborder
, int win_gravity
) {
1043 switch( win_gravity
) {
1044 case XCB_GRAVITY_NORTH_WEST
:
1045 case XCB_GRAVITY_WEST
:
1046 case XCB_GRAVITY_SOUTH_WEST
:
1051 case XCB_GRAVITY_NORTH
:
1052 case XCB_GRAVITY_CENTER
:
1053 case XCB_GRAVITY_SOUTH
:
1054 case XCB_GRAVITY_STATIC
:
1055 *cx
= fx
- cborder
+ FRAME_BORDER_WIDTH
+ FRAME_X_BORDER
;
1058 case XCB_GRAVITY_NORTH_EAST
:
1059 case XCB_GRAVITY_EAST
:
1060 case XCB_GRAVITY_SOUTH_EAST
:
1061 *cx
= fx
+ ( ( FRAME_BORDER_WIDTH
+ FRAME_X_BORDER
- cborder
) << 1 );
1065 switch( win_gravity
) {
1066 case XCB_GRAVITY_NORTH_WEST
:
1067 case XCB_GRAVITY_NORTH
:
1068 case XCB_GRAVITY_NORTH_EAST
:
1073 case XCB_GRAVITY_WEST
:
1074 case XCB_GRAVITY_CENTER
:
1075 case XCB_GRAVITY_EAST
:
1076 *cy
= fy
- cborder
+ ( ( FRAME_BORDER_WIDTH
+
1077 FRAME_TITLE_HEIGHT
) >> 1 ) + FRAME_X_BORDER
;
1080 case XCB_GRAVITY_SOUTH_WEST
:
1081 case XCB_GRAVITY_SOUTH
:
1082 case XCB_GRAVITY_SOUTH_EAST
:
1083 *cy
= fy
- ( cborder
<< 1 ) + FRAME_TITLE_HEIGHT
+ FRAME_BORDER_WIDTH
+
1084 ( FRAME_X_BORDER
<< 1 );
1087 case XCB_GRAVITY_STATIC
:
1088 *cy
= fy
- cborder
+ FRAME_TITLE_HEIGHT
+ FRAME_X_BORDER
;
1093 extern void apply_size_constraints( struct gwm_window
*window
, int *width
,
1096 int eff_base_width
= window
->u
.managed
.base_width
?
1097 window
->u
.managed
.base_width
: window
->u
.managed
.min_width
,
1098 eff_base_height
= window
->u
.managed
.base_height
?
1099 window
->u
.managed
.base_height
: window
->u
.managed
.min_height
;
1101 /* Apply the minimum and maximum constraints. These are already known
1102 to be compatible. */
1103 if( *width
< window
->u
.managed
.min_width
)
1104 *width
= window
->u
.managed
.min_width
;
1106 if( *height
< window
->u
.managed
.min_height
)
1107 *height
= window
->u
.managed
.min_height
;
1109 if( *width
> window
->u
.managed
.max_width
)
1110 *width
= window
->u
.managed
.max_width
;
1112 if( *height
> window
->u
.managed
.max_height
)
1113 *height
= window
->u
.managed
.max_height
;
1115 /* Now round down each dimension to an integer multiple of increments.
1116 Rounding down cannot violate the maximum constraint, and since
1117 eff_base_* >= min_*, it will not reduce below the minimum constraint. */
1118 *width
-= ( *width
- eff_base_width
) % window
->u
.managed
.width_inc
;
1119 *height
-= ( *height
- eff_base_height
) % window
->u
.managed
.height_inc
;
1121 if( window
->u
.managed
.min_aspect_x
* *height
>
1122 window
->u
.managed
.min_aspect_y
* *width
) {
1123 /* Minimum aspect ratio violated. Attempt to either increase the
1124 width or decrease the height (whichever is a smaller change), but
1125 don't do either if it would go outside the min/max bounds.
1126 Both division operations are safe (min_aspect_y is always
1127 positive, and min_aspect_x must be positive if there is a
1128 violation). Note that an exact solution might not be possible
1129 (e.g. certain cases where the aspect ratio and increments are
1133 min_x
= ( window
->u
.managed
.min_aspect_x
* *height
+
1134 ( window
->u
.managed
.min_aspect_y
- 1 ) ) /
1135 window
->u
.managed
.min_aspect_y
+ window
->u
.managed
.width_inc
- 1;
1136 min_x
-= ( min_x
- eff_base_width
) % window
->u
.managed
.width_inc
;
1138 max_y
= window
->u
.managed
.min_aspect_y
* *width
/
1139 window
->u
.managed
.min_aspect_x
;
1140 max_y
-= ( max_y
- eff_base_height
) % window
->u
.managed
.height_inc
;
1142 if( min_x
- *width
< *height
- max_y
) {
1143 /* The width change is smaller: prefer it if possible. */
1144 if( min_x
>= window
->u
.managed
.min_width
)
1146 else if( max_y
< window
->u
.managed
.max_height
)
1149 /* The height change is smaller: prefer it if possible. */
1150 if( max_y
< window
->u
.managed
.max_height
)
1152 else if( min_x
>= window
->u
.managed
.min_width
)
1157 if( window
->u
.managed
.max_aspect_x
* *height
<
1158 window
->u
.managed
.max_aspect_y
* *width
) {
1159 /* Maximum aspect ratio violated. Much like the case above... */
1162 min_y
= ( window
->u
.managed
.max_aspect_y
* *width
+
1163 ( window
->u
.managed
.max_aspect_x
- 1 ) ) /
1164 window
->u
.managed
.max_aspect_x
+ window
->u
.managed
.height_inc
- 1;
1165 min_y
-= ( min_y
- eff_base_height
) % window
->u
.managed
.height_inc
;
1167 max_x
= window
->u
.managed
.max_aspect_x
* *height
/
1168 window
->u
.managed
.max_aspect_y
;
1169 max_x
-= ( max_x
- eff_base_width
) % window
->u
.managed
.width_inc
;
1171 if( min_y
- *height
< *width
- max_x
) {
1172 /* The height change is smaller: prefer it if possible. */
1173 if( min_y
>= window
->u
.managed
.min_height
)
1175 else if( max_x
< window
->u
.managed
.max_width
)
1178 /* The width change is smaller: prefer it if possible. */
1179 if( max_x
< window
->u
.managed
.max_width
)
1181 else if( min_y
>= window
->u
.managed
.min_height
)
1187 static void stop_listening( struct gwm_window
*window
) {
1191 /* Ignore Window errors on the child, because it might be destroyed
1192 before we are notified. */
1195 handle_error_reply( xcb_change_window_attributes_checked(
1196 c
, window
->w
, XCB_CW_EVENT_MASK
, &value
),
1200 if( have_extension
[ EXT_SHAPE
] )
1201 handle_error_reply( xcb_shape_select_input_checked( c
, window
->w
,
1207 static void set_managed_state( struct gwm_window
*window
, int state
) {
1209 uint32_t values
[ 2 ];
1211 assert( window
->type
== WINDOW_MANAGED
);
1213 /* Place a WM_STATE property on the client window (ICCCM 2.0, section
1215 values
[ 0 ] = window
->u
.managed
.state
= state
;
1216 values
[ 1 ] = XCB_NONE
;
1217 xcb_change_property( c
, XCB_PROP_MODE_REPLACE
, window
->w
,
1218 atoms
[ ATOM_WM_STATE
], atoms
[ ATOM_WM_STATE
],
1222 static void place_window( struct gwm_window
*window
, int *x
, int *y
,
1223 int width
, int height
) {
1225 int sx
= screens
[ window
->screen
]->width_in_pixels
- width
;
1226 int sy
= screens
[ window
->screen
]->height_in_pixels
- height
;
1227 unsigned long long hash
;
1232 hash
= window
->w
* 0x9B4A36D1;
1240 hash
= window
->w
* 0x9B4A36D1;
1246 static void start_managing_window( struct gwm_window
*window
,
1247 xcb_get_window_attributes_reply_t
*attr
,
1248 xcb_get_geometry_reply_t
*geom
,
1250 xcb_shape_query_extents_reply_t
*shape
,
1252 xcb_get_property_reply_t
*props
[],
1256 uint32_t values
[ 4 ];
1257 struct gwm_window
*frame
, *button
;
1258 xcb_window_t w
= window
->w
;
1260 frame
= add_window( xcb_generate_id( c
) );
1261 button
= add_window( xcb_generate_id( c
) );
1262 window
->screen
= lookup_window( geom
->root
)->screen
;
1263 window
->type
= WINDOW_MANAGED
;
1264 window
->u
.managed
.frame
= frame
;
1265 window
->u
.managed
.border_width
= geom
->border_width
;
1266 window
->u
.managed
.cmap
= attr
->colormap
;
1267 window
->u
.managed
.hints
= 0;
1268 window
->u
.managed
.name
= NULL
;
1269 window
->u
.managed
.net_wm_name
= 0;
1270 window
->u
.managed
.state
= STATE_WITHDRAWN
;
1271 frame
->screen
= window
->screen
;
1272 frame
->type
= WINDOW_FRAME
;
1273 frame
->u
.frame
.child
= window
;
1274 frame
->u
.frame
.button
= button
;
1275 button
->screen
= window
->screen
;
1276 button
->type
= WINDOW_BUTTON
;
1277 button
->u
.button
.frame
= frame
;
1279 for( i
= 0; i
< NUM_PROPS
; i
++ )
1280 managed_property_change( window
, i
, props
[ i
] );
1282 translate_child_to_frame( &frame
->u
.frame
.x
, &frame
->u
.frame
.y
,
1283 &frame
->u
.frame
.width
, &frame
->u
.frame
.height
,
1284 geom
->x
, geom
->y
, geom
->width
, geom
->height
,
1286 window
->u
.managed
.win_gravity
);
1288 if( map_request
&& !( window
->u
.managed
.hints
& HINT_POSITION
) ) {
1291 place_window( window
, &x
, &y
,
1292 frame
->u
.frame
.width
, frame
->u
.frame
.height
);
1294 frame
->u
.frame
.x
= x
;
1295 frame
->u
.frame
.y
= y
;
1298 /* Don't create frames entirely off-screen; ensure that at least 8 pixels
1299 are within the root. */
1300 if( frame
->u
.frame
.x
> screens
[ frame
->screen
]->width_in_pixels
- 8 &&
1301 frame
->u
.frame
.x
+ frame
->u
.frame
.width
>
1302 screens
[ frame
->screen
]->width_in_pixels
)
1303 frame
->u
.frame
.x
= screens
[ frame
->screen
]->width_in_pixels
- 8;
1304 else if( frame
->u
.frame
.x
< 0 &&
1305 frame
->u
.frame
.x
+ frame
->u
.frame
.width
< 8 )
1306 frame
->u
.frame
.x
= 8 - frame
->u
.frame
.width
;
1308 if( frame
->u
.frame
.y
> screens
[ frame
->screen
]->height_in_pixels
- 8 &&
1309 frame
->u
.frame
.y
+ frame
->u
.frame
.height
>
1310 screens
[ frame
->screen
]->height_in_pixels
)
1311 frame
->u
.frame
.y
= screens
[ frame
->screen
]->height_in_pixels
- 8;
1312 else if( frame
->u
.frame
.y
< 0 &&
1313 frame
->u
.frame
.y
+ frame
->u
.frame
.height
< 8 )
1314 frame
->u
.frame
.y
= 8 - frame
->u
.frame
.height
;
1316 values
[ 0 ] = gwm_screens
[ frame
->screen
].pixels
[
1317 COL_FRAME_INACTIVE
]; /* background pixel */
1318 values
[ 1 ] = gwm_screens
[ frame
->screen
].pixels
[
1319 COL_BORDER
]; /* border pixel */
1320 values
[ 2 ] = XCB_GRAVITY_NORTH_WEST
; /* bit gravity */
1321 values
[ 3 ] = TRUE
; /* override redirect */
1322 values
[ 4 ] = XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_BUTTON_RELEASE
|
1323 XCB_EVENT_MASK_ENTER_WINDOW
| XCB_EVENT_MASK_POINTER_MOTION_HINT
|
1324 XCB_EVENT_MASK_BUTTON_MOTION
| XCB_EVENT_MASK_EXPOSURE
|
1325 XCB_EVENT_MASK_STRUCTURE_NOTIFY
| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
;
1327 xcb_create_window( c
, XCB_COPY_FROM_PARENT
, frame
->w
, geom
->root
,
1328 frame
->u
.frame
.x
, frame
->u
.frame
.y
,
1329 frame
->u
.frame
.width
, frame
->u
.frame
.height
, 1,
1330 XCB_WINDOW_CLASS_INPUT_OUTPUT
, XCB_COPY_FROM_PARENT
,
1331 XCB_CW_BACK_PIXEL
| XCB_CW_BORDER_PIXEL
|
1332 XCB_CW_BIT_GRAVITY
| XCB_CW_OVERRIDE_REDIRECT
|
1333 XCB_CW_EVENT_MASK
, values
);
1335 values
[ 0 ] = gwm_screens
[ frame
->screen
].pixels
[
1336 COL_BUTTON_INACTIVE
]; /* background pixel */
1337 values
[ 1 ] = gwm_screens
[ frame
->screen
].pixels
[
1338 COL_BORDER
]; /* border pixel */
1339 values
[ 2 ] = TRUE
; /* override redirect */
1340 values
[ 3 ] = XCB_EVENT_MASK_EXPOSURE
;
1341 xcb_create_window( c
, XCB_COPY_FROM_PARENT
, button
->w
, frame
->w
,
1342 2, 1, FRAME_BUTTON_SIZE
, FRAME_BUTTON_SIZE
, 1,
1343 XCB_WINDOW_CLASS_INPUT_OUTPUT
, XCB_COPY_FROM_PARENT
,
1344 XCB_CW_BACK_PIXEL
| XCB_CW_BORDER_PIXEL
|
1345 XCB_CW_OVERRIDE_REDIRECT
| XCB_CW_EVENT_MASK
, values
);
1347 xcb_grab_button( c
, FALSE
, button
->w
, XCB_EVENT_MASK_BUTTON_PRESS
|
1348 XCB_EVENT_MASK_BUTTON_RELEASE
|
1349 XCB_EVENT_MASK_ENTER_WINDOW
| XCB_EVENT_MASK_LEAVE_WINDOW
,
1350 XCB_GRAB_MODE_ASYNC
, XCB_GRAB_MODE_ASYNC
, XCB_NONE
,
1351 XCB_NONE
, XCB_BUTTON_INDEX_ANY
, 0x8000 );
1353 xcb_map_window( c
, button
->w
);
1355 xcb_change_save_set( c
, XCB_SET_MODE_INSERT
, w
);
1357 if( !map_request
) {
1358 /* If the window was mapped already, then reparenting it will
1359 generate an UnmapNotify. To avoid race conditions in discarding
1360 this unwanted event, temporarily reduce our event mask. */
1361 xcb_grab_server( c
);
1363 values
[ 0 ] = XCB_EVENT_MASK_PROPERTY_CHANGE
|
1364 XCB_EVENT_MASK_COLOR_MAP_CHANGE
;
1365 xcb_change_window_attributes( c
, w
, XCB_CW_EVENT_MASK
, values
);
1368 xcb_reparent_window( c
, w
, frame
->w
, FRAME_BORDER_WIDTH
,
1369 FRAME_TITLE_HEIGHT
);
1370 if( !map_request
) {
1371 values
[ 0 ] = XCB_EVENT_MASK_STRUCTURE_NOTIFY
|
1372 XCB_EVENT_MASK_PROPERTY_CHANGE
|
1373 XCB_EVENT_MASK_COLOR_MAP_CHANGE
;
1374 xcb_change_window_attributes( c
, w
, XCB_CW_EVENT_MASK
, values
);
1376 xcb_ungrab_server( c
);
1379 if( geom
->border_width
) {
1381 xcb_configure_window( c
, w
, XCB_CONFIG_WINDOW_BORDER_WIDTH
, values
);
1385 if( have_extension
[ EXT_SHAPE
] && shape
&& shape
->bounding_shaped
)
1386 match_managed_shape( window
, TRUE
);
1389 /* Tell the client we've relocated their window with respect to the
1390 root (see ICCCM 2.0, section 4.2.3); we almost certainly have,
1391 between reparenting it and possibly applying a placement policy. */
1392 synthetic_configure_notify( frame
);
1394 set_managed_state( window
, map_request
&&
1395 ( window
->u
.managed
.hints
& HINT_ICONIC
) ?
1396 STATE_ICONIC
: STATE_NORMAL
);
1398 if( window
->u
.managed
.state
== STATE_NORMAL
) {
1400 xcb_map_window( c
, w
);
1402 xcb_map_window( c
, frame
->w
);
1405 /* An ugly hack to get the button cursor properly set now that
1406 the button has been created. */
1407 managed_property_change( window
, PROP_WM_PROTOCOLS
,
1408 props
[ PROP_WM_PROTOCOLS
] );
1412 struct gwm_window
*window
; /* type WINDOW_INCOMPLETE */
1414 xcb_get_geometry_cookie_t geometry_cookie
;
1416 xcb_shape_query_extents_cookie_t shape_cookie
;
1418 xcb_get_property_cookie_t prop_cookies
[ NUM_PROPS
];
1421 static void handle_manage_window( unsigned int sequence
, void *reply
,
1422 xcb_generic_error_t
*error
,
1423 union callback_param cp
) {
1425 struct new_window
*nw
= cp
.p
;
1427 xcb_get_window_attributes_reply_t
*attr
= reply
;
1428 xcb_get_geometry_reply_t
*geom
= xcb_get_geometry_reply(
1429 c
, nw
->geometry_cookie
, NULL
);
1431 xcb_shape_query_extents_reply_t
*shape
= have_extension
[ EXT_SHAPE
] ?
1432 xcb_shape_query_extents_reply( c
, nw
->shape_cookie
, NULL
) : NULL
;
1434 xcb_get_property_reply_t
*props
[ NUM_PROPS
];
1437 for( i
= 0, have_all_props
= TRUE
; i
< NUM_PROPS
; i
++ )
1438 if( !( props
[ i
] = xcb_get_property_reply( c
, nw
->prop_cookies
[ i
],
1440 have_all_props
= FALSE
;
1443 show_error( error
);
1448 if( attr
&& geom
&& have_all_props
&&
1449 attr
->_class
== XCB_WINDOW_CLASS_INPUT_OUTPUT
&&
1450 ( nw
->map_request
|| ( attr
->map_state
== XCB_MAP_STATE_VIEWABLE
&&
1451 !attr
->override_redirect
) ) )
1452 start_managing_window( nw
->window
, attr
, geom
,
1456 props
, nw
->map_request
);
1458 stop_listening( nw
->window
);
1459 forget_window( nw
->window
);
1473 for( i
= 0; i
< NUM_PROPS
; i
++ )
1480 extern void manage_window( xcb_window_t w
, int map_request
) {
1482 struct new_window
*nw
= xmalloc( sizeof *nw
);
1483 uint32_t values
[ 2 ];
1485 union callback_param cp
;
1488 if( !( nw
->window
= add_window( w
) ) ) {
1489 /* The window already exists. This can happen if a client attempts
1490 more than one MapWindow before we've handle the first one, and
1491 so we receive multiple MapRequests. */
1496 /* Our request for notification of events concerning the client
1497 window must be atomic with our query of its state (otherwise
1498 there will exist some interval where we will be unaware of updates,
1499 or receive notifications for updates which were already present in
1500 the originally queried state). Unfortunately this means that we're
1501 forced to receive events before we even know whether we're interested
1502 in managing the window. If we subsequently discover that we
1503 didn't want the events after all, we will call stop_listening()
1504 (below), and any corresponding events for the unknown window
1505 will be silently ignored. */
1506 xcb_grab_server( c
);
1508 nw
->map_request
= map_request
;
1509 nw
->geometry_cookie
= xcb_get_geometry( c
, w
);
1511 if( have_extension
[ EXT_SHAPE
] )
1512 nw
->shape_cookie
= xcb_shape_query_extents( c
, w
);
1514 for( i
= 0; i
< NUM_PROPS
; i
++ )
1515 nw
->prop_cookies
[ i
] = xcb_get_property( c
, FALSE
, w
, prop_atoms
[ i
],
1519 seq
= xcb_get_window_attributes( c
, w
).sequence
;
1521 values
[ 0 ] = XCB_GRAVITY_NORTH_WEST
;
1522 values
[ 1 ] = XCB_EVENT_MASK_STRUCTURE_NOTIFY
|
1523 XCB_EVENT_MASK_PROPERTY_CHANGE
|
1524 XCB_EVENT_MASK_COLOR_MAP_CHANGE
;
1525 xcb_change_window_attributes( c
, w
, XCB_CW_WIN_GRAVITY
| XCB_CW_EVENT_MASK
,
1529 if( have_extension
[ EXT_SHAPE
] )
1530 xcb_shape_select_input( c
, w
, TRUE
);
1533 xcb_ungrab_server( c
);
1536 handle_async_reply( seq
, handle_manage_window
, cp
);
1538 nw
->window
->type
= WINDOW_INCOMPLETE
;
1539 nw
->window
->u
.incomplete
.sequence
= seq
;
1542 static void handle_destroy_window( unsigned int sequence
, void *reply
,
1543 xcb_generic_error_t
*error
,
1544 union callback_param cp
) {
1550 show_error( error
);
1555 forget_window( cp
.p
);
1558 extern void unmanage_window( struct gwm_window
*window
) {
1560 xcb_window_t w
= window
->w
;
1561 struct gwm_window
*frame
= window
->u
.managed
.frame
;
1562 uint32_t border_width
= window
->u
.managed
.border_width
;
1564 union callback_param cp
;
1566 if( focus_frame
== frame
)
1569 stop_listening( window
);
1571 /* Ignore Window errors on the child, because it might be destroyed
1572 before we are notified. */
1574 handle_error_reply( xcb_change_save_set_checked(
1575 c
, XCB_SET_MODE_DELETE
, w
), ERR_MASK_WINDOW
);
1577 /* Reparent the window back to the root; delete its WM_STATE
1578 property (see ICCCM 4.1.4); and map it if it was iconic. */
1579 translate_frame_to_child( &x
, &y
, frame
->u
.frame
.x
, frame
->u
.frame
.y
,
1580 border_width
, window
->u
.managed
.win_gravity
);
1581 handle_error_reply( xcb_reparent_window_checked(
1582 c
, w
, screens
[ window
->screen
]->root
,
1583 x
, y
), ERR_MASK_WINDOW
);
1585 handle_error_reply( xcb_delete_property_checked(
1586 c
, w
, atoms
[ ATOM_WM_STATE
] ), ERR_MASK_WINDOW
);
1588 handle_error_reply( xcb_configure_window_checked(
1589 c
, w
, XCB_CONFIG_WINDOW_BORDER_WIDTH
,
1590 &border_width
), ERR_MASK_WINDOW
);
1592 if( window
->u
.managed
.state
== STATE_ICONIC
)
1593 handle_error_reply( xcb_map_window_checked( c
, w
), ERR_MASK_WINDOW
);
1596 handle_async_reply( xcb_destroy_window_checked( c
, frame
->w
).sequence
,
1597 handle_destroy_window
, cp
);
1599 if( window
->u
.managed
.name
)
1600 free( window
->u
.managed
.name
);
1602 forget_window( window
);
1603 forget_window( frame
->u
.frame
.button
);
1605 /* We can't completely dismiss the frame window yet. Until our
1606 DestroyWindow is handled, other events might still arrive for
1607 it: in particular, we might see a MapRequest indicating a
1608 transition back to the Normal state before the adoption by the
1609 root has occurred. */
1610 frame
->type
= WINDOW_CHILDLESS
;
1613 extern void iconic_to_normal( struct gwm_window
*window
) {
1615 assert( window
->type
== WINDOW_MANAGED
);
1617 set_managed_state( window
, STATE_NORMAL
);
1618 xcb_map_window( c
, window
->w
);
1619 xcb_map_window( c
, window
->u
.managed
.frame
->w
);
1622 extern void deactivate_focus_frame( void ) {
1629 n
= gwm_screens
[ focus_frame
->screen
].pixels
[ COL_FRAME_INACTIVE
];
1630 xcb_change_window_attributes( c
, focus_frame
->w
, XCB_CW_BACK_PIXEL
, &n
);
1632 queue_window_update( focus_frame
, 0, 0, focus_frame
->u
.frame
.width
,
1633 focus_frame
->u
.frame
.height
, FALSE
);
1636 extern void generic_expose( struct gwm_window
*window
,
1637 xcb_expose_event_t
*ev
) {
1639 queue_window_update( window
, ev
->x
, ev
->y
, ev
->width
, ev
->height
, TRUE
);
1642 xcb_window_t passive_grab
; /* the window which has the pointer automatically
1643 grabbed via a button press, or XCB_NONE */
1645 static void handle_fake_change_property( unsigned int sequence
, void *reply
,
1646 xcb_generic_error_t
*error
,
1647 union callback_param cp
) {
1649 xcb_selection_notify_event_t
*p
= cp
.p
;
1656 p
->property
= XCB_NONE
;
1659 handle_error_reply( xcb_send_event_checked( c
, FALSE
, p
->requestor
, 0,
1660 (char *) p
), ERR_MASK_WINDOW
);
1665 static void fake_selection_request( struct gwm_window
*window
,
1666 xcb_selection_request_event_t
*ev
) {
1668 if( ev
->time
!= XCB_CURRENT_TIME
&& ev
->time
< window
->u
.fake
.timestamp
&&
1669 ev
->target
== atoms
[ ATOM_VERSION
] ) {
1670 /* ICCCM version identification: ICCCM 2.0, section 4.3. */
1671 static const uint32_t values
[ 2 ] = { 2, 0 };
1672 struct xcb_selection_notify_event_t
*p
= xmalloc( sizeof *p
);
1673 union callback_param cp
;
1675 p
->requestor
= ev
->requestor
;
1676 p
->selection
= ev
->selection
;
1677 p
->target
= ev
->target
;
1678 p
->property
= ev
->property
;
1681 handle_async_reply( xcb_change_property_checked(
1682 c
, XCB_PROP_MODE_REPLACE
, ev
->requestor
,
1683 atoms
[ ATOM_VERSION
], INTEGER
, 32, 2,
1684 values
).sequence
, handle_fake_change_property
,
1687 struct xcb_selection_notify_event_t
not;
1689 not.requestor
= ev
->requestor
;
1690 not.selection
= ev
->selection
;
1691 not.target
= ev
->target
;
1692 not.property
= XCB_NONE
;
1693 not.time
= ev
->time
;
1695 handle_error_reply( xcb_send_event_checked( c
, FALSE
, not.requestor
,
1701 static event_handler fake_handlers
[] = {
1704 NULL
, /* KeyPress */
1705 NULL
, /* KeyRelease */
1706 NULL
, /* ButtonPress */
1707 NULL
, /* ButtonRelease */
1708 NULL
, /* MotionNotify */
1709 NULL
, /* EnterNotify */
1710 NULL
, /* LeaveNotify */
1712 NULL
, /* FocusOut */
1713 NULL
, /* KeymapNotify */
1715 NULL
, /* GraphicsExpose */
1716 NULL
, /* NoExposure */
1717 NULL
, /* VisibilityNotify */
1718 NULL
, /* CreateNotify */
1719 NULL
, /* DestroyNotify */
1720 NULL
, /* UnmapNotify */
1721 NULL
, /* MapNotify */
1722 NULL
, /* MapRequest */
1723 NULL
, /* ReparentNotify */
1724 NULL
, /* ConfigureNotify */
1725 NULL
, /* ConfigureRequest */
1726 NULL
, /* GravityNotify */
1727 NULL
, /* ResizeRequest */
1728 NULL
, /* CirculateNotify */
1729 NULL
, /* CirculateRequest */
1730 NULL
, /* PropertyNotify */
1731 NULL
, /* SelectionClear */
1732 (event_handler
) fake_selection_request
, /* SelectionRequest */
1733 NULL
, /* SelectionNotify */
1734 NULL
, /* ColormapNotify */
1735 NULL
, /* ClientMessage */
1736 NULL
, /* MappingNotify */
1737 NULL
, /* (synthetic) */
1738 NULL
/* ShapeNotify */
1741 static event_handler feedback_handlers
[] = {
1744 NULL
, /* KeyPress */
1745 NULL
, /* KeyRelease */
1746 NULL
, /* ButtonPress */
1747 NULL
, /* ButtonRelease */
1748 NULL
, /* MotionNotify */
1749 NULL
, /* EnterNotify */
1750 NULL
, /* LeaveNotify */
1752 NULL
, /* FocusOut */
1753 NULL
, /* KeymapNotify */
1754 (event_handler
) generic_expose
,
1755 NULL
, /* GraphicsExpose */
1756 NULL
, /* NoExposure */
1757 NULL
, /* VisibilityNotify */
1758 NULL
, /* CreateNotify */
1759 NULL
, /* DestroyNotify */
1760 NULL
, /* UnmapNotify */
1761 NULL
, /* MapNotify */
1762 NULL
, /* MapRequest */
1763 NULL
, /* ReparentNotify */
1764 NULL
, /* ConfigureNotify */
1765 NULL
, /* ConfigureRequest */
1766 NULL
, /* GravityNotify */
1767 NULL
, /* ResizeRequest */
1768 NULL
, /* CirculateNotify */
1769 NULL
, /* CirculateRequest */
1770 NULL
, /* PropertyNotify */
1771 NULL
, /* SelectionClear */
1772 NULL
, /* SelectionRequest */
1773 NULL
, /* SelectionNotify */
1774 NULL
, /* ColormapNotify */
1775 NULL
, /* ClientMessage */
1776 NULL
, /* MappingNotify */
1777 NULL
/* (synthetic) */
1780 static const event_handler
*handlers
[] = {
1781 root_handlers
, managed_handlers
, frame_handlers
, button_handlers
,
1782 fake_handlers
, feedback_handlers
, NULL
, childless_handlers
1785 static void setup_display( void ) {
1788 xcb_intern_atom_cookie_t atom_cookies
[ NUM_ATOMS
];
1789 const xcb_setup_t
*setup
;
1790 xcb_screen_iterator_t iter
;
1791 xcb_intern_atom_cookie_t
*screen_atom_cookies
;
1793 xcb_get_selection_owner_cookie_t
*screen_owner_cookies
;
1794 xcb_window_t
*screen_owners
;
1795 xcb_client_message_event_t msg
;
1796 xcb_void_cookie_t
*cookies
;
1797 xcb_query_tree_cookie_t
*tree_cookies
;
1799 table_init( &windows
);
1800 table_init( &update_windows
);
1802 c
= xcb_connect( NULL
, NULL
);
1804 if( xcb_connection_has_error( c
) )
1805 fatal( "could not connect to server" );
1807 for( i
= 0; i
< NUM_EXTENSIONS
; i
++ )
1808 xcb_prefetch_extension_data( c
, (xcb_extension_t
*) extensions
[ i
] );
1810 for( i
= 0; i
< NUM_ATOMS
; i
++ )
1811 atom_cookies
[ i
] = xcb_intern_atom( c
, 0, strlen( atom_names
[ i
] ),
1814 setup
= xcb_get_setup( c
);
1816 iter
= xcb_setup_roots_iterator( setup
);
1817 num_screens
= iter
.rem
;
1818 screens
= xmalloc( num_screens
* sizeof *screens
);
1819 gwm_screens
= xmalloc( num_screens
* sizeof *gwm_screens
);
1820 screen_atom_cookies
= alloca( num_screens
* sizeof *screen_atom_cookies
);
1821 screen_owners
= alloca( num_screens
* sizeof *screen_owners
);
1822 screen_owner_cookies
= alloca( num_screens
* sizeof *screen_owner_cookies
);
1823 cookies
= alloca( num_screens
* sizeof *cookies
);
1824 tree_cookies
= alloca( num_screens
* sizeof *tree_cookies
);
1826 for( i
= 0; iter
.rem
; i
++, xcb_screen_next( &iter
) ) {
1827 static char wm_atom_str
[ 16 ] = "WM_S";
1828 xcb_depth_iterator_t depth_iter
;
1830 screens
[ i
] = iter
.data
;
1831 screen_atom_cookies
[ i
] =
1832 xcb_intern_atom( c
, 0, 4 + sprintf( wm_atom_str
+ 4, "%d", i
),
1835 for( depth_iter
= xcb_screen_allowed_depths_iterator( screens
[ i
] );
1836 depth_iter
.rem
; xcb_depth_next( &depth_iter
) ) {
1837 xcb_visualtype_iterator_t visual_iter
;
1839 for( visual_iter
= xcb_depth_visuals_iterator( depth_iter
.data
);
1840 visual_iter
.rem
; xcb_visualtype_next( &visual_iter
) ) {
1841 xcb_visualtype_t
*visual
= visual_iter
.data
;
1843 if( screens
[ i
]->root_visual
== visual
->visual_id
) {
1844 gwm_screens
[ i
].root_visual
= visual
;
1849 fatal( "Root visual for screen %d (0x%X) not found", i
,
1850 screens
[ i
]->root_visual
);
1855 fake_window
= add_window( xcb_generate_id( c
) );
1856 fake_window
->screen
= 0;
1857 fake_window
->type
= WINDOW_FAKE
;
1859 n
= XCB_EVENT_MASK_PROPERTY_CHANGE
;
1860 xcb_create_window( c
, 0, fake_window
->w
, screens
[ 0 ]->root
, 0, 0, 1, 1, 0,
1861 XCB_WINDOW_CLASS_INPUT_ONLY
, screens
[ 0 ]->root_visual
,
1862 XCB_CW_EVENT_MASK
, &n
);
1864 /* Obtain a timestamp we can use for our selection acquiry time
1865 (ICCCM 2.0, section 2.1). */
1866 xcb_change_property( c
, XCB_PROP_MODE_APPEND
, fake_window
->w
, WM_HINTS
,
1867 WM_HINTS
, 32, 0, NULL
);
1869 xcb_flush( c
); /* BLOCK */
1871 for( i
= 0; i
< NUM_EXTENSIONS
; i
++ ) {
1872 const xcb_query_extension_reply_t
*r
;
1874 if( ( have_extension
[ i
] =
1875 ( r
= xcb_get_extension_data(
1876 c
, (xcb_extension_t
*) extensions
[ i
] ) ) && r
->present
) ) {
1877 extension_event
[ i
] = r
->first_event
;
1878 extension_error
[ i
] = r
->first_error
;
1882 for( i
= 0; i
< NUM_ATOMS
; i
++ ) {
1883 xcb_intern_atom_reply_t
*r
=
1884 xcb_intern_atom_reply( c
, atom_cookies
[ i
], NULL
);
1886 atoms
[ i
] = r
->atom
;
1890 /* FIXME Also monitor EWMH client window properties:
1891 _MOTIF_WM_HINTS, to see what decoration clients want
1892 _NET_WM_NAME, UTF-8 in preference to WM_NAME
1893 _NET_WM_ICON, ARGB icon(s)
1894 and set _NET_FRAME_EXTENTS. */
1895 prop_atoms
[ PROP__NET_WM_NAME
] = atoms
[ ATOM__NET_WM_NAME
];
1896 prop_atoms
[ PROP_WM_COLORMAP_WINDOWS
] = atoms
[ ATOM_WM_COLORMAP_WINDOWS
];
1897 prop_atoms
[ PROP_WM_HINTS
] = WM_HINTS
;
1898 prop_atoms
[ PROP_WM_NAME
] = WM_NAME
;
1899 prop_atoms
[ PROP_WM_NORMAL_HINTS
] = WM_NORMAL_HINTS
;
1900 prop_atoms
[ PROP_WM_PROTOCOLS
] = atoms
[ ATOM_WM_PROTOCOLS
];
1902 prop_types
[ PROP__NET_WM_NAME
] = atoms
[ ATOM_UTF8_STRING
];
1903 prop_types
[ PROP_WM_COLORMAP_WINDOWS
] = WINDOW
;
1904 prop_types
[ PROP_WM_HINTS
] = WM_HINTS
;
1905 prop_types
[ PROP_WM_NAME
] = XCB_GET_PROPERTY_TYPE_ANY
;
1906 prop_types
[ PROP_WM_NORMAL_HINTS
] = WM_SIZE_HINTS
;
1907 prop_types
[ PROP_WM_PROTOCOLS
] = ATOM
;
1909 for( i
= 0; i
< num_screens
; i
++ ) {
1910 xcb_intern_atom_reply_t
*r
=
1911 xcb_intern_atom_reply( c
, screen_atom_cookies
[ i
], NULL
);
1913 gwm_screens
[ i
].wm_atom
= r
->atom
;
1918 if( have_extension
[ EXT_RENDER
] ) {
1919 xcb_render_query_version_cookie_t render_cookie
;
1920 xcb_render_query_pict_formats_cookie_t formats_cookie
;
1921 xcb_render_query_version_reply_t
*render_reply
;
1922 xcb_render_query_pict_formats_reply_t
*formats_reply
;
1923 xcb_render_pictforminfo_t
*formats
;
1925 xcb_render_pictscreen_iterator_t siter
;
1927 render_cookie
= xcb_render_query_version( c
, XCB_RENDER_MAJOR_VERSION
,
1928 XCB_RENDER_MINOR_VERSION
);
1930 formats_cookie
= xcb_render_query_pict_formats( c
);
1932 xcb_flush( c
); /* BLOCK */
1934 render_reply
= xcb_render_query_version_reply( c
, render_cookie
, NULL
);
1936 if( !render_reply
) {
1937 have_extension
[ EXT_RENDER
] = FALSE
;
1941 if( !render_reply
->major_version
&& render_reply
->minor_version
< 1 ) {
1942 /* We need at least version 0.1, for FillRectangles support. It's
1943 too inconvenient to initialise pictures to a constant colour
1944 without it (which seems to have been an oversight in the
1945 original specification of the protocol). */
1946 have_extension
[ EXT_RENDER
] = FALSE
;
1948 free( render_reply
);
1953 free( render_reply
);
1955 formats_reply
= xcb_render_query_pict_formats_reply( c
, formats_cookie
,
1958 formats
= xcb_render_query_pict_formats_formats( formats_reply
);
1960 for( i
= 0; i
< formats_reply
->num_formats
; i
++ )
1961 if( formats
[ i
].type
== XCB_RENDER_PICT_TYPE_DIRECT
&&
1962 formats
[ i
].direct
.alpha_mask
== 0xFF ) {
1963 if( !formats
[ i
].direct
.red_mask
&&
1964 !formats
[ i
].direct
.green_mask
&&
1965 !formats
[ i
].direct
.blue_mask
) {
1966 fmt_a8
= formats
[ i
].id
;
1967 memcpy( &dfmt_a8
, &formats
[ i
].direct
, sizeof dfmt_a8
);
1968 } else if( formats
[ i
].direct
.red_mask
== 0xFF &&
1969 formats
[ i
].direct
.green_mask
== 0xFF &&
1970 formats
[ i
].direct
.blue_mask
== 0xFF ) {
1971 fmt_rgba8
= formats
[ i
].id
;
1972 memcpy( &dfmt_rgba8
, &formats
[ i
].direct
,
1973 sizeof dfmt_rgba8
);
1977 if( !fmt_a8
|| !fmt_rgba8
)
1978 /* These two pictformats are guaranteed by the RENDER protocol
1979 (section 7). We can't cope without them. */
1980 have_extension
[ EXT_RENDER
] = FALSE
;
1982 for( i
= 0, siter
= xcb_render_query_pict_formats_screens_iterator(
1983 formats_reply
); siter
.rem
;
1984 i
++, xcb_render_pictscreen_next( &siter
) ) {
1985 xcb_render_pictdepth_iterator_t diter
;
1987 for( diter
= xcb_render_pictscreen_depths_iterator( siter
.data
);
1988 diter
.rem
; xcb_render_pictdepth_next( &diter
) ) {
1989 xcb_render_pictvisual_iterator_t viter
;
1991 for( viter
= xcb_render_pictdepth_visuals_iterator(
1992 diter
.data
); viter
.rem
;
1993 xcb_render_pictvisual_next( &viter
) )
1994 if( viter
.data
->visual
==
1995 gwm_screens
[ i
].root_visual
->visual_id
) {
1996 gwm_screens
[ i
].root_pictformat
= viter
.data
->format
;
2004 free( formats_reply
);
2012 if( have_extension
[ EXT_RENDER
] ) {
2013 decorate_render_init();
2014 update_window
= render_update_window
;
2018 decorate_core_init();
2019 update_window
= core_update_window
;
2022 /* Listen for a selection timestamp. */
2025 xcb_generic_event_t
*ev
= wait_for_event();
2028 fatal( "did not receive property notification" );
2030 if( ev
->response_type
& SEND_EVENT_MASK
) {
2033 continue; /* ignore synthetic events */
2034 } else if( ev
->response_type
== XCB_PROPERTY_NOTIFY
) {
2035 xcb_property_notify_event_t
*not =
2036 (xcb_property_notify_event_t
*) ev
;
2038 if( not->window
!= fake_window
->w
||
2039 not->atom
!= WM_HINTS
||
2040 not->state
!= XCB_PROPERTY_NEW_VALUE
) {
2041 warning( "unexpected property notification" );
2046 fake_window
->u
.fake
.timestamp
= not->time
;
2051 } else if( ev
->response_type
!= XCB_MAPPING_NOTIFY
) {
2052 /* Ignore MappingNotify -- we'll query the mappings later. */
2053 warning( "unexpected event while waiting for property "
2065 xcb_change_window_attributes( c
, fake_window
->w
, XCB_CW_EVENT_MASK
, &n
);
2068 int existing_manager
;
2069 int managers_changed
;
2070 sigset_t sigs
, oldsigs
;
2072 for( i
= 0; i
< num_screens
; i
++ )
2073 screen_owner_cookies
[ i
] =
2074 xcb_get_selection_owner( c
, gwm_screens
[ i
].wm_atom
);
2076 xcb_flush( c
); /* BLOCK */
2078 existing_manager
= 0;
2079 for( i
= 0; i
< num_screens
; i
++ ) {
2080 xcb_get_selection_owner_reply_t
*r
=
2081 xcb_get_selection_owner_reply( c
, screen_owner_cookies
[ i
],
2084 if( ( screen_owners
[ i
] = r
->owner
) != XCB_NONE
) {
2086 printf( "Screen %d is already managed.\n", i
);
2094 if( existing_manager
&& !flag_replace
) {
2095 printf( "Use \"%s --replace\" to replace any existing "
2096 "window manager(s).\n", argv0
);
2101 if( existing_manager
) {
2104 /* Wait for existing manager(s) to terminate (ICCCM 2.0,
2106 n
= XCB_EVENT_MASK_STRUCTURE_NOTIFY
;
2107 for( i
= 0; i
< num_screens
; i
++ )
2108 if( screen_owners
[ i
] != XCB_NONE
)
2109 xcb_change_window_attributes( c
, screen_owners
[ i
],
2110 XCB_CW_EVENT_MASK
, &n
);
2112 for( i
= 0; i
< num_screens
; i
++ )
2113 screen_owner_cookies
[ i
] =
2114 xcb_get_selection_owner( c
, gwm_screens
[ i
].wm_atom
);
2116 xcb_flush( c
); /* BLOCK */
2118 managers_changed
= FALSE
;
2119 for( i
= 0; i
< num_screens
; i
++ ) {
2120 xcb_get_selection_owner_reply_t
*r
=
2121 xcb_get_selection_owner_reply( c
, screen_owner_cookies
[ i
],
2124 if( r
->owner
!= screen_owners
[ i
] )
2125 managers_changed
= TRUE
;
2130 if( managers_changed
) {
2131 /* Argh. A window manager changed during all our bookkeeping.
2132 Undo our notifications, and start again. */
2134 for( i
= 0; i
< num_screens
; i
++ )
2135 if( screen_owners
[ i
] != XCB_NONE
)
2136 xcb_change_window_attributes( c
, screen_owners
[ i
],
2137 XCB_CW_EVENT_MASK
, &n
);
2143 /* Acquire ownership of WM_Sn selections (ICCCM 2.0, section 4.3). */
2144 for( i
= 0; i
< num_screens
; i
++ ) {
2145 xcb_set_selection_owner( c
, fake_window
->w
,
2146 gwm_screens
[ i
].wm_atom
,
2147 fake_window
->u
.fake
.timestamp
);
2148 screen_owner_cookies
[ i
] =
2149 xcb_get_selection_owner( c
, gwm_screens
[ i
].wm_atom
);
2152 xcb_flush( c
); /* BLOCK */
2153 sync_with_callback( screen_owner_cookies
[ num_screens
- 1 ].sequence
);
2155 for( i
= 0; i
< num_screens
; i
++ ) {
2156 xcb_get_selection_owner_reply_t
*r
=
2157 xcb_get_selection_owner_reply( c
, screen_owner_cookies
[ i
],
2160 if( r
->owner
!= fake_window
->w
)
2161 fatal( "did not acquire window manager selection" );
2168 /* Listen for DestroyNotify on any selection holders. */
2169 while( existing_manager
) {
2170 static int killed_existing_managers
;
2172 xcb_generic_event_t
*ev
= wait_for_event(); /* BLOCK */
2175 if( flag_force
&& signal_caught
== SIGALRM
&&
2176 !killed_existing_managers
) {
2179 for( i
= 0; i
< num_screens
; i
++ )
2180 if( screen_owners
[ i
] )
2181 xcb_kill_client( c
, screen_owners
[ i
] );
2183 killed_existing_managers
= TRUE
;
2190 fatal( "did not receive destroy notification" );
2193 if( ev
->response_type
& SEND_EVENT_MASK
) {
2196 continue; /* ignore synthetic events */
2197 } else if( ev
->response_type
== XCB_DESTROY_NOTIFY
) {
2198 xcb_destroy_notify_event_t
*not =
2199 (xcb_destroy_notify_event_t
*) ev
;
2201 for( i
= 0; i
< num_screens
; i
++ )
2202 if( not->window
== screen_owners
[ i
] ) {
2203 screen_owners
[ i
] = XCB_NONE
;
2208 } else if( ev
->response_type
== XCB_SELECTION_CLEAR
)
2209 /* We lost a window manager selection before we even
2210 started. Just shut down quietly. */
2212 else if( ev
->response_type
== XCB_MAPPING_NOTIFY
)
2213 /* Ignore MappingNotify. */
2215 else if( !ev
->response_type
) {
2217 xcb_generic_error_t
*error
= (xcb_generic_error_t
*) ev
;
2219 /* Expect Window and Value errors, because old window managers
2220 might die before we sent a corresponding request. */
2221 if( error
->error_code
!= XCB_WINDOW
&&
2222 error
->error_code
!= XCB_VALUE
)
2223 show_error( error
);
2227 warning( "unexpected event while waiting for destroy "
2238 sigfillset( &sigs
);
2239 sigprocmask( SIG_BLOCK
, &sigs
, &oldsigs
);
2242 if( signal_caught
== SIGALRM
)
2245 sigprocmask( SIG_SETMASK
, &oldsigs
, NULL
);
2248 msg
.response_type
= XCB_CLIENT_MESSAGE
;
2251 msg
.type
= atoms
[ ATOM_MANAGER
];
2252 msg
.data
.data32
[ 0 ] = fake_window
->u
.fake
.timestamp
;
2253 /* Data32[ 1 ] varies; will be completed later. */
2254 msg
.data
.data32
[ 2 ] = fake_window
->w
;
2255 msg
.data
.data32
[ 3 ] = 0;
2256 msg
.data
.data32
[ 4 ] = 0;
2258 n
= XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_BUTTON_RELEASE
|
2259 XCB_EVENT_MASK_ENTER_WINDOW
| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
;
2261 for( i
= 0; i
< num_screens
; i
++ ) {
2262 /* Send StructureNotify ClientMessage (ICCCM 2.0, section 2.8). */
2263 msg
.window
= screens
[ i
]->root
;
2264 msg
.data
.data32
[ 1 ] = gwm_screens
[ i
].wm_atom
;
2265 xcb_send_event( c
, FALSE
, screens
[ i
]->root
,
2266 XCB_EVENT_MASK_STRUCTURE_NOTIFY
, (char *) &msg
);
2269 xcb_change_window_attributes_checked( c
, screens
[ i
]->root
,
2270 XCB_CW_EVENT_MASK
, &n
);
2272 tree_cookies
[ i
] = xcb_query_tree( c
, screens
[ i
]->root
);
2275 xcb_set_input_focus( c
, XCB_INPUT_FOCUS_NONE
, XCB_INPUT_FOCUS_POINTER_ROOT
,
2276 fake_window
->u
.fake
.timestamp
);
2278 get_keyboard_mapping( setup
->min_keycode
,
2279 setup
->max_keycode
- setup
->min_keycode
+ 1 );
2280 get_modifier_mapping();
2282 xcb_flush( c
); /* BLOCK */
2283 sync_with_callback( cookies
[ num_screens
- 1 ].sequence
);
2285 for( i
= 0; i
< num_screens
; i
++ ) {
2286 xcb_generic_error_t
*error
= xcb_request_check( c
, cookies
[ i
] );
2287 xcb_query_tree_reply_t
*r
;
2288 struct gwm_window
*window
;
2289 xcb_window_t
*children
;
2293 fatal( "could not acquire SubstructureRedirect" );
2295 window
= add_window( screens
[ i
]->root
);
2297 window
->type
= WINDOW_ROOT
;
2299 gwm_screens
[ i
].cmap
= XCB_NONE
;
2300 gwm_screens
[ i
].cmap_time
= fake_window
->u
.fake
.timestamp
;
2302 r
= xcb_query_tree_reply( c
, tree_cookies
[ i
], NULL
);
2303 children
= xcb_query_tree_children( r
);
2304 for( j
= 0; j
< r
->children_len
; j
++ )
2305 manage_window( children
[ j
], FALSE
);
2311 static void handle_mapping_notify( xcb_mapping_notify_event_t
*ev
) {
2313 switch( ev
->request
) {
2314 case XCB_MAPPING_MODIFIER
:
2315 get_modifier_mapping();
2318 case XCB_MAPPING_KEYBOARD
:
2319 get_keyboard_mapping( ev
->first_keycode
, ev
->count
);
2322 case XCB_MAPPING_POINTER
:
2327 static void handle_client_message( xcb_client_message_event_t
*ev
) {
2329 if( ev
->type
== atoms
[ ATOM_WM_CHANGE_STATE
] ) {
2330 struct gwm_window
*window
;
2332 if( ( window
= lookup_window( ev
->window
) ) &&
2333 window
->type
== WINDOW_MANAGED
&&
2334 window
->u
.managed
.state
== STATE_NORMAL
&&
2335 ev
->data
.data32
[ 0 ] == STATE_ICONIC
) {
2338 set_managed_state( window
, STATE_ICONIC
);
2339 /* We need to unmap our child without invoking the normal
2340 UnmapNotify handler response. Unfortunately it's very
2341 difficult to communicate to the handler the distinction
2342 between the resultant UnmapNotify from an unmap here
2343 and an unrelated event. Checking the request sequence
2344 is not robust, because asynchronous events might not be
2345 assigned unique sequence numbers. Instead, we grab
2346 the server and temporarily change our event mask, which
2347 seems a heavyweight approach but does guarantee that
2348 only this event will be ignored. */
2349 xcb_grab_server( c
);
2351 n
= XCB_EVENT_MASK_PROPERTY_CHANGE
|
2352 XCB_EVENT_MASK_COLOR_MAP_CHANGE
;
2353 xcb_change_window_attributes( c
, window
->w
, XCB_CW_EVENT_MASK
, &n
);
2355 xcb_unmap_window( c
, window
->w
);
2357 n
= XCB_EVENT_MASK_STRUCTURE_NOTIFY
|
2358 XCB_EVENT_MASK_PROPERTY_CHANGE
|
2359 XCB_EVENT_MASK_COLOR_MAP_CHANGE
;
2360 xcb_change_window_attributes( c
, window
->w
, XCB_CW_EVENT_MASK
, &n
);
2362 xcb_ungrab_server( c
);
2364 xcb_unmap_window( c
, window
->u
.managed
.frame
->w
);
2366 } else if( ev
->type
== atoms
[ ATOM_WM_COLORMAP_NOTIFY
] ) {
2367 struct gwm_window
*window
;
2369 if( ( window
= lookup_window( ev
->window
) ) &&
2370 window
->type
== WINDOW_ROOT
&&
2371 !ev
->data
.data32
[ 1 ] )
2372 /* Ignore the case where the client is starting installation:
2373 if it is well-behaved, it will do so only when the pointer
2374 is grabbed (in which case we won't do anything to conflict
2375 with it); if it is not well-behaved, it's not worth trying
2376 to accomodate it. */
2377 install_window_colormap( window
->screen
, focus_frame
?
2378 focus_frame
->u
.frame
.child
: NULL
,
2379 ev
->data
.data32
[ 0 ] );
2382 /* FIXME and all the EWMH client messages:
2384 _NET_MOVE_RESIZE_WINDOW
2387 _NET_REQUEST_FRAME_EXTENTS */
2390 static void handle_events( void ) {
2392 xcb_generic_event_t
*ev
;
2394 /* FIXME Should read as many events as possible (loop on
2395 xcb_poll_for_event), and queue them... might be able to
2396 elide some event processing that way if later events
2397 supercede earlier ones. */
2398 while( ( ev
= wait_for_event() ) ) {
2399 int event_type
= ev
->response_type
;
2400 int event_base_type
= event_type
& 0x7F;
2403 /* Determine event window and time. */
2404 switch( event_base_type
) {
2407 case XCB_KEYMAP_NOTIFY
:
2408 case XCB_CLIENT_MESSAGE
:
2409 case XCB_MAPPING_NOTIFY
:
2414 w
= ( (xcb_key_press_event_t
*) ev
)->event
;
2415 latest_timestamp
= ( (xcb_key_press_event_t
*) ev
)->time
;
2418 case XCB_KEY_RELEASE
:
2419 w
= ( (xcb_key_release_event_t
*) ev
)->event
;
2420 latest_timestamp
= ( (xcb_key_release_event_t
*) ev
)->time
;
2423 case XCB_BUTTON_PRESS
:
2424 w
= ( (xcb_button_press_event_t
*) ev
)->event
;
2425 latest_timestamp
= ( (xcb_button_press_event_t
*) ev
)->time
;
2428 case XCB_BUTTON_RELEASE
:
2429 w
= ( (xcb_button_release_event_t
*) ev
)->event
;
2430 latest_timestamp
= ( (xcb_button_release_event_t
*) ev
)->time
;
2433 case XCB_MOTION_NOTIFY
:
2434 w
= ( (xcb_motion_notify_event_t
*) ev
)->event
;
2435 latest_timestamp
= ( (xcb_motion_notify_event_t
*) ev
)->time
;
2438 case XCB_ENTER_NOTIFY
:
2439 case XCB_LEAVE_NOTIFY
:
2440 w
= ( (xcb_enter_notify_event_t
*) ev
)->event
;
2441 latest_timestamp
= ( (xcb_enter_notify_event_t
*) ev
)->time
;
2446 w
= ( (xcb_focus_in_event_t
*) ev
)->event
;
2450 w
= ( (xcb_expose_event_t
*) ev
)->window
;
2453 case XCB_GRAPHICS_EXPOSURE
:
2454 w
= ( (xcb_graphics_exposure_event_t
*) ev
)->drawable
;
2457 case XCB_NO_EXPOSURE
:
2458 w
= ( (xcb_no_exposure_event_t
*) ev
)->drawable
;
2461 case XCB_VISIBILITY_NOTIFY
:
2462 w
= ( (xcb_visibility_notify_event_t
*) ev
)->window
;
2465 case XCB_CREATE_NOTIFY
:
2466 w
= ( (xcb_create_notify_event_t
*) ev
)->parent
;
2469 case XCB_DESTROY_NOTIFY
:
2470 w
= ( (xcb_destroy_notify_event_t
*) ev
)->event
;
2473 case XCB_UNMAP_NOTIFY
:
2474 w
= ( (xcb_unmap_notify_event_t
*) ev
)->event
;
2477 case XCB_MAP_NOTIFY
:
2478 w
= ( (xcb_map_notify_event_t
*) ev
)->event
;
2481 case XCB_MAP_REQUEST
:
2482 w
= ( (xcb_map_request_event_t
*) ev
)->parent
;
2485 case XCB_REPARENT_NOTIFY
:
2486 w
= ( (xcb_reparent_notify_event_t
*) ev
)->event
;
2489 case XCB_CONFIGURE_NOTIFY
:
2490 w
= ( (xcb_configure_notify_event_t
*) ev
)->event
;
2493 case XCB_CONFIGURE_REQUEST
:
2494 w
= ( (xcb_configure_request_event_t
*) ev
)->parent
;
2497 case XCB_GRAVITY_NOTIFY
:
2498 w
= ( (xcb_gravity_notify_event_t
*) ev
)->event
;
2501 case XCB_RESIZE_REQUEST
:
2502 w
= ( (xcb_resize_request_event_t
*) ev
)->window
;
2505 case XCB_CIRCULATE_NOTIFY
:
2506 case XCB_CIRCULATE_REQUEST
:
2507 w
= ( (xcb_circulate_notify_event_t
*) ev
)->event
;
2510 case XCB_PROPERTY_NOTIFY
:
2511 w
= ( (xcb_property_notify_event_t
*) ev
)->window
;
2512 latest_timestamp
= ( (xcb_property_notify_event_t
*) ev
)->time
;
2515 case XCB_SELECTION_CLEAR
:
2516 w
= ( (xcb_selection_clear_event_t
*) ev
)->owner
;
2517 latest_timestamp
= ( (xcb_selection_clear_event_t
*) ev
)->time
;
2520 case XCB_SELECTION_REQUEST
:
2521 w
= ( (xcb_selection_request_event_t
*) ev
)->owner
;
2522 if( ( (xcb_selection_request_event_t
*) ev
)->time
)
2524 ( (xcb_selection_request_event_t
*) ev
)->time
;
2527 case XCB_SELECTION_NOTIFY
:
2528 w
= ( (xcb_selection_notify_event_t
*) ev
)->requestor
;
2529 if( ( (xcb_selection_notify_event_t
*) ev
)->time
)
2531 ( (xcb_selection_notify_event_t
*) ev
)->time
;
2534 case XCB_COLORMAP_NOTIFY
:
2535 w
= ( (xcb_colormap_notify_event_t
*) ev
)->window
;
2540 if( event_base_type
== extension_event
[ EXT_SHAPE
] ) {
2541 w
= ( (xcb_shape_notify_event_t
*) ev
)->affected_window
;
2542 event_type
= SHAPE_NOTIFY
;
2546 warning( "Unknown event" );
2550 if( ev
->response_type
& 0x80 )
2551 event_type
= SYNTHETIC_EVENT
;
2553 /* Global handling for events (before specific window handler). */
2554 switch( event_type
) {
2556 show_error( (xcb_generic_error_t
*) ev
);
2560 warning( "handle_events: received reply message" );
2563 case XCB_BUTTON_PRESS
:
2564 if( initial_press( (xcb_button_press_event_t
*) ev
)
2569 case XCB_KEYMAP_NOTIFY
:
2572 case XCB_SELECTION_CLEAR
:
2573 /* We've lost the manager selection (see ICCCM 2.0, section
2574 2.8); close down gracefully. */
2578 case XCB_MAPPING_NOTIFY
:
2579 handle_mapping_notify( (xcb_mapping_notify_event_t
*) ev
);
2582 case SYNTHETIC_EVENT
:
2583 if( ev
->response_type
== ( XCB_CLIENT_MESSAGE
| SEND_EVENT_MASK
) )
2584 handle_client_message( (xcb_client_message_event_t
*) ev
);
2588 if( w
!= XCB_NONE
) {
2589 struct gwm_window
*window
;
2590 void ( *handler
)( struct gwm_window
*, xcb_generic_event_t
* );
2592 if( ( window
= lookup_window( w
) ) &&
2593 handlers
[ window
->type
] &&
2594 ( handler
= handlers
[ window
->type
][ event_type
] ) )
2595 handler( window
, ev
);
2598 /* Global handling for events (after specific window handler). */
2599 switch( event_type
) {
2600 case XCB_BUTTON_RELEASE
:
2601 if( final_release( (xcb_button_release_event_t
*) ev
) )
2602 passive_grab
= XCB_NONE
;
2606 /* FIXME other features:
2607 - make override-redirect windows translucent
2608 - desktop menu & other key/pointer bindings
2609 - when in the window menu, turn all windows translucent
2610 except for the selected item (temporarily raise that one?)
2611 - root background/cursor? (no; xsetroot for non composite case)
2612 - standard colourmaps? (no; xstdcmap does that)
2614 - add debugging structure to detect memory leaks, and maybe even
2615 correlating X protocol errors to request call sites
2616 - other extensions... XINERAMA? RANDR? RandR should send
2617 ConfigureNotify to the root when resolution changes, so don't
2618 need the extension... test it. */
2624 static void handle_intern_atom( unsigned int sequence
, void *reply
,
2625 xcb_generic_error_t
*error
,
2626 union callback_param p
) {
2635 static void shutdown_display( void ) {
2639 xcb_intern_atom_cookie_t cookie
;
2640 union callback_param cp
;
2641 xcb_query_tree_cookie_t
*tree_cookies
=
2642 alloca( num_screens
* sizeof *tree_cookies
);
2644 /* Query the window list from the server, so that we can reparent
2645 the windows bottom-to-top and therefore avoid disturbing the
2646 stacking order. Of course, if we kept stacking order information
2647 ourselves, this query would not be necessary. */
2648 for( i
= 0; i
< num_screens
; i
++ )
2649 tree_cookies
[ i
] = xcb_query_tree( c
, screens
[ i
]->root
);
2651 xcb_flush( c
); /* BLOCK */
2653 for( i
= 0; i
< num_screens
; i
++ ) {
2654 xcb_query_tree_reply_t
*r
;
2655 xcb_window_t
*children
;
2656 struct gwm_window
*window
;
2659 r
= xcb_query_tree_reply( c
, tree_cookies
[ i
], NULL
);
2660 children
= xcb_query_tree_children( r
);
2661 for( j
= 0; j
< r
->children_len
; j
++ )
2662 if( ( window
= lookup_window( children
[ j
] ) ) &&
2663 window
->type
== WINDOW_FRAME
)
2664 unmanage_window( window
->u
.frame
.child
);
2669 /* The ICCCM says that using CurrentTime for SetInputFocus is naughty
2670 (section 4.2.7). But we don't have much alternative in this case:
2671 if we're shutting down in response to a signal, for instance. */
2672 xcb_set_input_focus( c
, XCB_INPUT_FOCUS_NONE
, XCB_INPUT_FOCUS_POINTER_ROOT
,
2676 for( i
= 0; i
< num_screens
; i
++ )
2677 xcb_change_window_attributes( c
, screens
[ i
]->root
,
2678 XCB_CW_EVENT_MASK
, &n
);
2681 if( have_extension
[ EXT_RENDER
] )
2682 decorate_render_done();
2685 decorate_core_done();
2687 /* A pointless request to generate a reply, so we'll know when we've
2688 processed everything the server will send us. */
2689 cookie
= xcb_intern_atom( c
, TRUE
, 4, "ATOM" );
2691 handle_async_reply( cookie
.sequence
, handle_intern_atom
, cp
);
2695 sync_with_callback( cookie
.sequence
);
2697 xcb_disconnect( c
);
2699 /* Be pedantic about deallocating memory, to assist any tools that are
2700 looking for leaks. */
2701 forget_window( fake_window
);
2704 for( i
= 0; i
< windows
.used
; i
++ )
2705 if( windows
.values
[ i
]->type
== WINDOW_ROOT
) {
2706 forget_window( windows
.values
[ i
] );
2711 /* All windows have now been deallocated -- release the tables, too. */
2712 table_destroy( &windows
);
2713 table_destroy( &update_windows
);
2718 free( gwm_screens
);
2728 static void unknown( char *opt
) {
2730 printf( "%s: unknown option %s\n"
2731 "Usage: %s [OPTION]...\n"
2732 "Try \"%s --help\" for more information.\n",
2733 argv0
, opt
, argv0
, argv0
);
2736 extern int main( int argc
, char *argv
[] ) {
2739 struct sigaction sa
;
2741 static int flag_help
, flag_version
;
2742 static const struct option
{
2747 { "debug", &flag_debug
},
2749 { "force", &flag_force
},
2750 { "help", &flag_help
},
2751 { "replace", &flag_replace
},
2752 { "version", &flag_version
}
2754 #define NUM_OPTIONS ( sizeof options / sizeof *options )
2756 #if DEBUG && HAVE_MTRACE
2762 for( i
= 1; i
< argc
; i
++ )
2763 if( argv
[ i
][ 0 ] == '-' ) {
2764 if( argv
[ i
][ 1 ] == '-' && argv
[ i
][ 2 ] ) {
2765 for( j
= 0; j
< NUM_OPTIONS
; j
++ )
2766 if( !strncmp( argv
[ i
] + 2, options
[ j
].opt
,
2767 strlen( argv
[ i
] + 2 ) ) ) {
2768 *options
[ j
].flag
= 1;
2772 if( j
== NUM_OPTIONS
) {
2773 unknown( argv
[ i
] );
2777 } else if( argv
[ i
][ 1 ] != '-' ) {
2778 for( j
= 0; j
< NUM_OPTIONS
; j
++ )
2779 if( argv
[ i
][ 1 ] == options
[ j
].opt
[ 0 ] &&
2781 *options
[ j
].flag
= 1;
2785 if( j
== NUM_OPTIONS
) {
2786 unknown( argv
[ i
] );
2792 unknown( argv
[ i
] );
2798 printf( "Usage: %s [OPTION]...\n"
2801 " -d, --debug Show all events received\n"
2803 " -f, --force If replacing another window manager, "
2804 "terminate it if necessary\n"
2805 " -h, --help Display this information and exit\n"
2806 " -r, --replace Take over window management if another "
2808 " -v, --version Display version information and exit\n"
2809 "Please send bug reports to <" PACKAGE_BUGREPORT
">.\n",
2815 if( flag_version
) {
2816 puts( PACKAGE_STRING
"\nPlease send bug reports to <"
2817 PACKAGE_BUGREPORT
">." );
2822 sa
.sa_handler
= catch_signal
;
2823 sigemptyset( &sa
.sa_mask
);
2825 sigaction( SIGHUP
, &sa
, NULL
);
2826 sigaction( SIGINT
, &sa
, NULL
);
2827 sigaction( SIGTERM
, &sa
, NULL
);
2829 sa
.sa_handler
= alarm_signal
;
2830 sigaction( SIGALRM
, &sa
, NULL
);
2832 sa
.sa_handler
= child_signal
;
2833 sa
.sa_flags
= SA_NOCLDSTOP
| SA_RESTART
;
2834 sigaction( SIGCHLD
, &sa
, NULL
);
2839 which_signal
= signal_caught
;
2840 signal_caught
= 0; /* acknowledge receipt, so we get normal event
2841 processing during shutdown */
2843 if( xcb_connection_has_error( c
) )
2844 /* Don't bother attempting orderly shutdown. */
2845 fatal( "server connection error" );
2849 if( which_signal
) {
2850 raise( which_signal
);
2858 /* FIXME xv moves when changing files (or pressing "n")... investigate. */