summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGary Wong <gtw@gnu.org>2010-11-09 12:12:44 -0700
committerGary Wong <gtw@gnu.org>2012-01-30 13:20:12 -0700
commitdfb575033ef2138bec13c0b08107e2ff7f30573f (patch)
treeaf67d38e2f394c0437dfdc20b69c9ec825babd9a
parent4c373f89d60630cc69e6ab4156734f1b3a431aec (diff)
Add support for RANDR extension.
Track screen and CRTC geometry changes, and use sensible window and menu placement policies when multiple CRTCs are present.
-rw-r--r--ChangeLog16
-rw-r--r--actions.c59
-rw-r--r--button.c6
-rw-r--r--configure.ac3
-rw-r--r--frame.c78
-rw-r--r--gwm.c395
-rw-r--r--gwm.h32
-rw-r--r--managed.c3
-rw-r--r--menu.c209
-rw-r--r--root.c57
10 files changed, 770 insertions, 88 deletions
diff --git a/ChangeLog b/ChangeLog
index c59aa35..99e8b47 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2010-11-09 Gary Wong <gtw@gnu.org>
+
+ * gwm.c (show_error) [USE_RANDR]: Show RANDR errors.
+ (update_crtc, handle_crtc_info) [USE_RANDR]: New functions.
+ (setup_display) [USE_RANDR]: Query CRTC information, if possible.
+ * root.c (root_configure_notify): Handle screen size changes.
+ (root_rr_crtc_change_notify) [USE_RANDR]: New function.
+
+ * frame.c (build_edges) [USE_RANDR]: Add CRTC edges to list.
+ * menu.c (fit_menu): New function.
+ * gwm.c (point_live, make_area_live): New functions.
+ (place_window) [USE_RANDR]: Keep windows within a CRTC, if possible.
+
+ * actions.c (window_list_enter): Make windows live upon selection.
+ (window_list_leave): Restore window position if it had been made live.
+
2010-11-05 Gary Wong <gtw@gnu.org>
* decorate-core.c (core_update_window): Ignore childless frames.
diff --git a/actions.c b/actions.c
index 84d1413..2ea1dbe 100644
--- a/actions.c
+++ b/actions.c
@@ -182,7 +182,7 @@ extern void action_window_menu( struct gwm_window *window,
}
static xcb_window_t place_holder;
-static int window_was_iconic;
+static int window_was_iconic, window_was_dead, dead_x, dead_y;
static void window_list_activate( struct gwm_window *window,
xcb_generic_event_t *ev,
@@ -201,7 +201,8 @@ static void window_list_enter( struct gwm_window *window,
struct gwm_window *client;
if( ( client = lookup_window( cp.l ) ) ) {
- uint32_t values[ 2 ];
+ uint32_t values[ 4 ];
+ int live_x, live_y;
/* Introduce another (unmapped and otherwise unused) window into the
stack to mark the original position of the client in the stacking
@@ -221,10 +222,28 @@ static void window_list_enter( struct gwm_window *window,
if( ( window_was_iconic = client->u.managed.state == STATE_ICONIC ) )
iconic_to_normal( client );
- /* Raise the client window to immediately below the menu. */
- values[ 0 ] = pointer_demux; /* the menu */
- values[ 1 ] = XCB_STACK_MODE_BELOW;
+ live_x = dead_x = client->u.managed.frame->u.frame.x;
+ live_y = dead_y = client->u.managed.frame->u.frame.y;
+
+ make_area_live( client->screen, &live_x, &live_y,
+ client->u.managed.frame->u.frame.width,
+ client->u.managed.frame->u.frame.height, 8, 8 );
+
+ if( ( window_was_dead = live_x != dead_x || live_y != dead_y ) ) {
+ values[ 0 ] = live_x;
+ values[ 1 ] = live_y;
+ values[ 2 ] = pointer_demux; /* the menu */
+ values[ 3 ] = XCB_STACK_MODE_BELOW;
+ } else {
+ values[ 0 ] = pointer_demux; /* the menu */
+ values[ 1 ] = XCB_STACK_MODE_BELOW;
+ }
+
xcb_configure_window( c, client->u.managed.frame->w,
+ window_was_dead ? XCB_CONFIG_WINDOW_X |
+ XCB_CONFIG_WINDOW_Y |
+ XCB_CONFIG_WINDOW_SIBLING |
+ XCB_CONFIG_WINDOW_STACK_MODE :
XCB_CONFIG_WINDOW_SIBLING |
XCB_CONFIG_WINDOW_STACK_MODE, values );
@@ -245,16 +264,38 @@ static void window_list_leave( struct gwm_window *window,
struct gwm_window *client;
if( ( client = lookup_window( cp.l ) ) ) {
- uint32_t values[ 2 ];
-
- values[ 0 ] = place_holder;
- values[ 1 ] = XCB_STACK_MODE_ABOVE;
+ uint32_t values[ 4 ];
+
+ if( window_was_dead ) {
+ values[ 0 ] = dead_x;
+ values[ 1 ] = dead_y;
+ values[ 2 ] = place_holder;
+ values[ 3 ] = XCB_STACK_MODE_ABOVE;
+ } else {
+ values[ 0 ] = place_holder;
+ values[ 1 ] = XCB_STACK_MODE_ABOVE;
+ }
xcb_configure_window( c, client->u.managed.frame->w,
+ window_was_dead ?
+ XCB_CONFIG_WINDOW_X |
+ XCB_CONFIG_WINDOW_Y |
+ XCB_CONFIG_WINDOW_SIBLING |
+ XCB_CONFIG_WINDOW_STACK_MODE :
XCB_CONFIG_WINDOW_SIBLING |
XCB_CONFIG_WINDOW_STACK_MODE, values );
if( window_was_iconic )
normal_to_iconic( client );
+
+ if( focus_frame == client->u.managed.frame ) {
+ deactivate_focus_frame();
+
+ xcb_set_input_focus( c, XCB_INPUT_FOCUS_NONE,
+ XCB_INPUT_FOCUS_POINTER_ROOT,
+ event_time( ev ) );
+
+ focus_frame = NULL;
+ }
}
if( place_holder ) {
diff --git a/button.c b/button.c
index bfcfda5..07cb9d5 100644
--- a/button.c
+++ b/button.c
@@ -156,7 +156,7 @@ static void button_leave_notify( struct gwm_window *window,
set_button_active( window, FALSE );
}
-const event_handler button_handlers[] = {
+const event_handler button_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -192,5 +192,7 @@ const event_handler button_handlers[] = {
NULL, /* ColormapNotify */
NULL, /* ClientMessage */
NULL, /* MappingNotify */
- NULL /* (synthetic) */
+ NULL, /* (synthetic) */
+ NULL, /* RRScreenChangeNotify */
+ NULL /* ShapeNotify */
};
diff --git a/configure.ac b/configure.ac
index 7342276..e6903f2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -56,12 +56,14 @@ PKG_PROG_PKG_CONFIG
# Checks for libraries:
AC_ARG_WITH(composite,[ --with-composite use the X Composite extension.])
AC_ARG_WITH(damage, [ --with-damage use the X DAMAGE extension.])
+AC_ARG_WITH(randr, [ --with-randr use the X RANDR extension.])
AC_ARG_WITH(render, [ --with-render use the X RENDER extension.])
AC_ARG_WITH(shape, [ --with-shape use the X SHAPE extension.])
AC_ARG_WITH(xfixes, [ --with-xfixes use the XFIXES extension.])
AH_TEMPLATE(USE_COMPOSITE,[Use the X Composite extension.])
AH_TEMPLATE(USE_DAMAGE,[Use the X DAMAGE extension.])
+AH_TEMPLATE(USE_RANDR,[Use the X RANDR extension.])
AH_TEMPLATE(USE_RENDER,[Use the X RENDER extension.])
AH_TEMPLATE(USE_SHAPE,[Use the X SHAPE extension.])
AH_TEMPLATE(USE_XFIXES,[Use the XFIXES extension.])
@@ -71,6 +73,7 @@ GWM_OPTIONAL_PACKAGE(fontconfig)
GWM_OPTIONAL_PACKAGE(freetype2)
GWM_OPTIONAL_PACKAGE(xcb-composite, composite)
GWM_OPTIONAL_PACKAGE(xcb-damage, damage)
+GWM_OPTIONAL_PACKAGE(xcb-randr, randr)
GWM_OPTIONAL_PACKAGE(xcb-render, render, fontconfig freetype2)
GWM_OPTIONAL_PACKAGE(xcb-shape, shape)
GWM_OPTIONAL_PACKAGE(xcb-xfixes, xfixes)
diff --git a/frame.c b/frame.c
index f70b796..9d5b4d7 100644
--- a/frame.c
+++ b/frame.c
@@ -439,30 +439,60 @@ static int v_edge_compare( const void *v0, const void *v1 ) {
static void build_edges( int screen ) {
struct gwm_window *window, **windowp, **end;
+ int num_crtcs;
assert( !t_edges );
assert( !b_edges );
assert( !l_edges );
assert( !r_edges );
- t_edges = xmalloc( ( windows.used + 1 ) * sizeof *t_edges );
- b_edges = xmalloc( ( windows.used + 1 ) * sizeof *b_edges );
- l_edges = xmalloc( ( windows.used + 1 ) * sizeof *l_edges );
- r_edges = xmalloc( ( windows.used + 1 ) * sizeof *r_edges );
-
- t_edges[ 0 ].x_min = b_edges[ 0 ].x_min = 0;
- t_edges[ 0 ].x_max = b_edges[ 0 ].x_max =
- screens[ screen ]->width_in_pixels;
- t_edges[ 0 ].y = screens[ screen ]->height_in_pixels;
- b_edges[ 0 ].y = 0;
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] )
+ num_crtcs = gwm_screens[ screen ].num_crtcs;
+ else
+#endif
+ num_crtcs = 1;
- l_edges[ 0 ].x = screens[ screen ]->width_in_pixels;
- r_edges[ 0 ].x = 0;
- l_edges[ 0 ].y_min = r_edges[ 0 ].y_min = 0;
- l_edges[ 0 ].y_max = r_edges[ 0 ].y_max =
- screens[ screen ]->height_in_pixels;
+ t_edges = xmalloc( ( windows.used + num_crtcs ) * sizeof *t_edges );
+ b_edges = xmalloc( ( windows.used + num_crtcs ) * sizeof *b_edges );
+ l_edges = xmalloc( ( windows.used + num_crtcs ) * sizeof *l_edges );
+ r_edges = xmalloc( ( windows.used + num_crtcs ) * sizeof *r_edges );
+
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ int i;
+ struct gwm_crtc **crtcs = gwm_screens[ screen ].crtcs;
+
+ for( i = 0; i < num_crtcs; i++ ) {
+ t_edges[ i ].x_min = b_edges[ i ].x_min = crtcs[ i ]->x;
+ t_edges[ i ].x_max = b_edges[ i ].x_max = crtcs[ i ]->x +
+ crtcs[ i ]->width;
+ t_edges[ i ].y = crtcs[ i ]->y + crtcs[ i ]->height;
+ b_edges[ i ].y = crtcs[ i ]->y;
+
+ l_edges[ i ].x = crtcs[ i ]->x + crtcs[ i ]->width;
+ r_edges[ i ].x = crtcs[ i ]->x;
+ l_edges[ i ].y_min = r_edges[ i ].y_min = crtcs[ i ]->y;
+ l_edges[ i ].y_max = r_edges[ i ].y_max = crtcs[ i ]->y +
+ crtcs[ i ]->height;
+ }
+ } else
+#endif
+ {
+ t_edges[ 0 ].x_min = b_edges[ 0 ].x_min = 0;
+ t_edges[ 0 ].x_max = b_edges[ 0 ].x_max =
+ screens[ screen ]->width_in_pixels;
+ t_edges[ 0 ].y = screens[ screen ]->height_in_pixels;
+ b_edges[ 0 ].y = 0;
+
+ l_edges[ 0 ].x = screens[ screen ]->width_in_pixels;
+ r_edges[ 0 ].x = 0;
+ l_edges[ 0 ].y_min = r_edges[ 0 ].y_min = 0;
+ l_edges[ 0 ].y_max = r_edges[ 0 ].y_max =
+ screens[ screen ]->height_in_pixels;
+ }
- num_t_edges = num_b_edges = num_l_edges = num_r_edges = 1;
+ num_t_edges = num_b_edges = num_l_edges = num_r_edges = num_crtcs;
end = windows.values + windows.used;
for( windowp = windows.values; windowp < end; windowp++ )
@@ -867,7 +897,11 @@ static void frame_motion_notify( struct gwm_window *window,
} else
return;
}
-
+
+ if( !point_live( window->screen, ev->root_x, ev->root_y ) )
+ /* Ignore motion events outside all CRTCs. */
+ return;
+
switch( window_op ) {
int old_t, old_b, old_l, old_r, new_t, new_b, new_l, new_r;
@@ -1146,7 +1180,7 @@ static void frame_configure_request( struct gwm_window *window,
synthetic_configure_notify( window );
}
-const event_handler frame_handlers[] = {
+const event_handler frame_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -1183,10 +1217,11 @@ const event_handler frame_handlers[] = {
NULL, /* ClientMessage */
NULL, /* MappingNotify */
NULL, /* (synthetic) */
- NULL /* ShapeNotify */
+ NULL, /* RRNotify */
+ NULL /* ShapeNotify */
};
-const event_handler childless_handlers[] = {
+const event_handler childless_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -1223,5 +1258,6 @@ const event_handler childless_handlers[] = {
NULL, /* ClientMessage */
NULL, /* MappingNotify */
NULL, /* (synthetic) */
- NULL /* ShapeNotify */
+ NULL, /* RRNotify */
+ NULL /* ShapeNotify */
};
diff --git a/gwm.c b/gwm.c
index 2f58a17..95cea02 100644
--- a/gwm.c
+++ b/gwm.c
@@ -53,6 +53,9 @@
#if USE_DAMAGE
#include <xcb/damage.h>
#endif
+#if USE_RANDR
+#include <xcb/randr.h>
+#endif
#if USE_RENDER
#include <xcb/render.h>
#endif
@@ -431,6 +434,20 @@ extern void show_error( xcb_generic_error_t *error ) {
break;
}
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] &&
+ error->error_code >= extension_error[ EXT_RANDR ] &&
+ error->error_code <= extension_error[ EXT_RANDR ] +
+ XCB_RANDR_BAD_MODE ) {
+ static const char *const randr_strings[ XCB_RANDR_BAD_MODE + 1 ] = {
+ "Output", "Crtc", "Mode"
+ };
+
+ type = randr_strings[ error->error_code -
+ extension_error[ EXT_RANDR ] ];
+ }
+#endif
+
#if USE_RENDER
if( have_extension[ EXT_RENDER ] &&
error->error_code >= extension_error[ EXT_RENDER ] &&
@@ -1013,6 +1030,88 @@ extern CONST xcb_timestamp_t event_time( xcb_generic_event_t *ev ) {
}
}
+extern int point_live( int screen, int x, int y ) {
+
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ int i;
+
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+
+ if( x >= c->x && x < c->x + c->width &&
+ y >= c->y && y < c->y + c->height )
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+#endif
+
+ return x >= 0 && x < screens[ screen ]->width_in_pixels &&
+ y >= 0 && y < screens[ screen ]->height_in_pixels;
+}
+
+extern void make_area_live( int screen, int *x, int *y, int width, int height,
+ int min_x, int min_y ) {
+
+ if( min_x < 0 || min_x > width )
+ min_x = width;
+
+ if( min_y < 0 || min_y > height )
+ min_y = height;
+
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ int i, best_x, best_y, best = INT_MAX;
+
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+ int xc = *x, yc = *y, diff;
+
+ if( min_x ) {
+ if( *x >= c->x + c->width - min_x )
+ xc = c->x + c->width - min_x - 1;
+ else if( *x < c->x && *x + width < c->x + min_x )
+ xc = c->x + min_x - width;
+ }
+
+ if( min_y ) {
+ if( *y >= c->y + c->height - min_y )
+ yc = c->y + c->height - min_y - 1;
+ else if( *y < c->y && *y + height < c->y + min_y )
+ yc = c->y + min_y - height;
+ }
+
+ if( ( diff = abs( xc - *x ) + abs( yc - *y ) ) < best ) {
+ best_x = xc;
+ best_y = yc;
+ best = diff;
+ }
+ }
+
+ *x = best_x;
+ *y = best_y;
+
+ return;
+ }
+#endif
+
+ if( min_x ) {
+ if( *x >= screens[ screen ]->width_in_pixels - min_x )
+ *x = screens[ screen ]->width_in_pixels - min_x - 1;
+ else if( *x < 0 && *x + width < min_x )
+ *x = min_x - width;
+ }
+
+ if( min_y ) {
+ if( *y >= screens[ screen ]->height_in_pixels - min_y )
+ *y = screens[ screen ]->height_in_pixels - min_y - 1;
+ else if( *y < 0 && *y + height < min_y )
+ *y = min_y - height;
+ }
+}
+
static void stop_listening( struct gwm_window *window ) {
uint32_t value;
@@ -1036,26 +1135,77 @@ static void stop_listening( struct gwm_window *window ) {
static void place_window( struct gwm_window *window, int *x, int *y,
int width, int height ) {
- int sx = screens[ window->screen ]->width_in_pixels - width;
- int sy = screens[ window->screen ]->height_in_pixels - height;
+ int sx, sy, xoff, yoff;
#if defined( UINT64_MAX ) || defined( uint64_t )
uint64_t hash;
#else
unsigned long hash;
#endif
-
+
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ int i;
+ unsigned long *scores, chosen, total = 0;
+
+ scores = alloca( gwm_screens[ window->screen ].num_crtcs *
+ sizeof *scores );
+
+ /* Search for CRTCs big enough to hold the window. */
+ for( i = 0; i < gwm_screens[ window->screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ window->screen ].crtcs[ i ];
+
+ if( width <= c->width && height <= c->height )
+ total += c->width + c->height - width - height;
+
+ scores[ i ] = total;
+ }
+
+ if( total ) {
+ struct gwm_crtc *c;
+
+ /* Choose from among the candidates. */
+ hash = window->w * 0xC27E3B59;
+#if defined( UINT64_MAX ) || defined( uint64_t ) || ULONG_MAX > 0xFFFFFFFFUL
+ hash ^= hash >> 32;
+#endif
+
+ chosen = hash % total;
+
+ for( i = 0; scores[ i ] < chosen; i++ )
+ ;
+
+ c = gwm_screens[ window->screen ].crtcs[ i ];
+
+ xoff = c->x;
+ yoff = c->y;
+ sx = c->width - width;
+ sy = c->height - height;
+ } else {
+ /* No CRTC is big enough to hold the window. */
+ xoff = yoff = 0;
+ sx = screens[ window->screen ]->width_in_pixels - width;
+ sy = screens[ window->screen ]->height_in_pixels - height;
+ }
+ } else
+#endif
+ {
+ xoff = yoff = 0;
+ sx = screens[ window->screen ]->width_in_pixels - width;
+ sy = screens[ window->screen ]->height_in_pixels - height;
+ }
+
if( sx <= 0 )
- *x = 0;
+ *x = xoff;
else {
hash = window->w * 0x9B4A36D1;
#if defined( UINT64_MAX ) || defined( uint64_t ) || ULONG_MAX > 0xFFFFFFFFUL
hash ^= hash >> 32;
#endif
- *x = hash % sx;
+ *x = xoff + hash % sx;
}
if( sy <= 0 )
- *y = 0;
+ *y = yoff;
else {
hash = window->w * 0xA6E34925;
#if defined( UINT64_MAX ) || defined( uint64_t ) || ULONG_MAX > 0xFFFFFFFFUL
@@ -1130,23 +1280,8 @@ static void start_managing_window( struct gwm_window *window,
frame->u.frame.y = y;
}
- /* Don't create frames entirely off-screen; ensure that at least 8 pixels
- are within the root. */
- if( frame->u.frame.x > screens[ frame->screen ]->width_in_pixels - 8 &&
- frame->u.frame.x + frame->u.frame.width >
- screens[ frame->screen ]->width_in_pixels )
- frame->u.frame.x = screens[ frame->screen ]->width_in_pixels - 8;
- else if( frame->u.frame.x < 0 &&
- frame->u.frame.x + frame->u.frame.width < 8 )
- frame->u.frame.x = 8 - frame->u.frame.width;
-
- if( frame->u.frame.y > screens[ frame->screen ]->height_in_pixels - 8 &&
- frame->u.frame.y + frame->u.frame.height >
- screens[ frame->screen ]->height_in_pixels )
- frame->u.frame.y = screens[ frame->screen ]->height_in_pixels - 8;
- else if( frame->u.frame.y < 0 &&
- frame->u.frame.y + frame->u.frame.height < 8 )
- frame->u.frame.y = 8 - frame->u.frame.height;
+ make_area_live( frame->screen, &frame->u.frame.x, &frame->u.frame.y,
+ frame->u.frame.width, frame->u.frame.height, 8, 8 );
values[ 0 ] = gwm_screens[ frame->screen ].pixels[
COL_FRAME_INACTIVE ]; /* background pixel */
@@ -1522,7 +1657,7 @@ static void fake_selection_request( struct gwm_window *window,
}
}
-static const event_handler fake_handlers[] = {
+static const event_handler fake_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -1559,8 +1694,9 @@ static const event_handler fake_handlers[] = {
NULL, /* ClientMessage */
NULL, /* MappingNotify */
NULL, /* (synthetic) */
- NULL /* ShapeNotify */
-}, feedback_handlers[] = {
+ NULL, /* RRNotify */
+ NULL /* ShapeNotify */
+}, feedback_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -1596,7 +1732,9 @@ static const event_handler fake_handlers[] = {
NULL, /* ColormapNotify */
NULL, /* ClientMessage */
NULL, /* MappingNotify */
- NULL /* (synthetic) */
+ NULL, /* (synthetic) */
+ NULL, /* RRNotify */
+ NULL /* ShapeNotify */
};
static const event_handler *const handlers[] = {
@@ -1605,6 +1743,93 @@ static const event_handler *const handlers[] = {
childless_handlers
};
+#if USE_RANDR
+struct crtc_callback {
+ int screen;
+ int crtc;
+ int *ok;
+};
+
+extern void update_crtc( int screen, int crtc, int width, int height,
+ int x, int y ) {
+
+ struct gwm_screen *s = gwm_screens + screen;
+ int i;
+
+ if( width && height ) {
+ for( i = 0; i < s->num_crtcs; i++ )
+ if( s->crtcs[ i ]->crtc == crtc )
+ goto found;
+
+ s->crtcs = xrealloc( s->crtcs, ++s->num_crtcs * sizeof s->crtcs );
+ s->crtcs[ i ] = xmalloc( sizeof *s->crtcs[ i ] );
+
+ found:
+ s->crtcs[ i ]->crtc = crtc;
+ s->crtcs[ i ]->x = x;
+ s->crtcs[ i ]->y = y;
+ s->crtcs[ i ]->width = width;
+ s->crtcs[ i ]->height = height;
+ } else
+ for( i = 0; i < s->num_crtcs; i++ )
+ if( s->crtcs[ i ]->crtc == crtc ) {
+ free( s->crtcs[ i ] );
+ s->crtcs[ i ] = s->crtcs[ --s->num_crtcs ];
+ s->crtcs = xrealloc( s->crtcs, s->num_crtcs *
+ sizeof s->crtcs );
+
+ return;
+ }
+}
+
+static INIT void handle_crtc_info( unsigned int sequence, void *reply,
+ xcb_generic_error_t *error,
+ union callback_param cp ) {
+
+ struct crtc_callback *cc = cp.p;
+
+ if( error ) {
+ cc->ok = FALSE;
+
+ free( error );
+ }
+
+ if( reply ) {
+ /* A fixed version of the xcb_randr_get_crtc_info_reply_t
+ definition. Unfortunately XCB's randr protocol was incorrect
+ until commit c8129e1f19ceb2002c9764b94f03564600f13a8f. Rather
+ than checking for a defective randr.h, we simply define the
+ correct structure here. */
+ struct fixed_xcb_randr_get_crtc_info_reply_t {
+ uint8_t response_type;
+ uint8_t status;
+ uint16_t sequence;
+ uint32_t length;
+ xcb_timestamp_t timestamp;
+ int16_t x;
+ int16_t y;
+ uint16_t width;
+ uint16_t height;
+ uint32_t mode;
+ uint16_t rotation;
+ uint16_t rotations;
+ uint16_t num_outputs;
+ uint16_t num_possible_outputs;
+ } *r = reply;
+
+ if( r->status == XCB_RANDR_SET_CONFIG_SUCCESS )
+ update_crtc( cc->screen, cc->crtc, r->width, r->height,
+ r->x, r->y );
+ else
+ *cc->ok = FALSE;
+
+ free( reply );
+ }
+
+ free( cc );
+}
+#endif
+
static INIT void setup_display( void ) {
static INITD const xcb_extension_t *const extensions[ EXTENSIONS_SIZE ] = {
@@ -1614,6 +1839,9 @@ static INIT void setup_display( void ) {
#if USE_DAMAGE
&xcb_damage_id,
#endif
+#if USE_RANDR
+ &xcb_randr_id,
+#endif
#if USE_RENDER
&xcb_render_id,
#endif
@@ -1635,6 +1863,10 @@ static INIT void setup_display( void ) {
xcb_client_message_event_t msg;
xcb_void_cookie_t *cookies;
xcb_query_tree_cookie_t *tree_cookies;
+#if USE_RANDR
+ xcb_randr_query_version_cookie_t randr_cookie;
+ xcb_randr_get_screen_resources_cookie_t *resources_cookies;
+#endif
table_init( &windows );
table_init( &update_windows );
@@ -1663,6 +1895,9 @@ static INIT void setup_display( void ) {
screen_owner_cookies = alloca( num_screens * sizeof *screen_owner_cookies );
cookies = alloca( num_screens * sizeof *cookies );
tree_cookies = alloca( num_screens * sizeof *tree_cookies );
+#if USE_RANDR
+ resources_cookies = alloca( num_screens * sizeof *resources_cookies );
+#endif
for( i = 0; iter.rem; i++, xcb_screen_next( &iter ) ) {
char wm_atom_str[ 16 ];
@@ -1706,6 +1941,12 @@ static INIT void setup_display( void ) {
}
}
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] )
+ randr_cookie = xcb_randr_query_version( c, XCB_RANDR_MAJOR_VERSION,
+ XCB_RANDR_MINOR_VERSION );
+#endif
+
for( i = 0; i < NUM_ATOMS; i++ ) {
xcb_intern_atom_reply_t *r =
xcb_intern_atom_reply( c, atom_cookies[ i ], NULL );
@@ -1999,6 +2240,23 @@ static INIT void setup_display( void ) {
sigprocmask( SIG_SETMASK, &oldsigs, NULL );
} while( 0 );
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ xcb_randr_query_version_reply_t *randr_reply;
+
+ if( ( randr_reply = xcb_randr_query_version_reply( c, randr_cookie,
+ NULL ) ) ) {
+ /* We need at least version 1.2 to obtain CRTC information. */
+ have_extension[ EXT_RANDR ] = randr_reply->major_version > 1 ||
+ ( randr_reply->major_version == 1 &&
+ randr_reply->minor_version >= 2 );
+
+ free( randr_reply );
+ } else
+ have_extension[ EXT_RANDR ] = 0;
+ }
+#endif
+
msg.response_type = XCB_CLIENT_MESSAGE;
msg.format = 32;
msg.sequence = 0;
@@ -2010,11 +2268,22 @@ static INIT void setup_display( void ) {
msg.data.data32[ 4 ] = 0;
n = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
- XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_OWNER_GRAB_BUTTON;
for( i = 0; i < num_screens; i++ ) {
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ xcb_randr_select_input( c, screens[ i ]->root,
+ XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE );
+
+ resources_cookies[ i ] =
+ xcb_randr_get_screen_resources( c, screens[ i ]->root );
+ }
+#endif
+
/* Send StructureNotify ClientMessage (ICCCM 2.0, section 2.8). */
msg.window = screens[ i ]->root;
msg.data.data32[ 1 ] = gwm_screens[ i ].wm_atom;
@@ -2084,8 +2353,62 @@ static INIT void setup_display( void ) {
get_modifier_mapping();
xcb_flush( c ); /* BLOCK */
- sync_with_callback( cookies[ num_screens - 1 ].sequence );
+ sync_with_callback( tree_cookies[ num_screens - 1 ].sequence );
+
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ int i, ok;
+ unsigned int sequence;
+ for( i = 0; i < num_screens; i++ ) {
+ gwm_screens[ i ].num_crtcs = 0;
+ gwm_screens[ i ].crtcs = NULL;
+ }
+
+ /* It's essential to have up to date CRTC information before
+ proceeding, because the placement policy when we adopt existing
+ windows depends on it. */
+ for( i = 0; i < num_screens; i++ ) {
+ xcb_randr_get_screen_resources_reply_t *r;
+ retry_race:
+ r = xcb_randr_get_screen_resources_reply( c, resources_cookies[ i ],
+ NULL );
+
+ if( r ) {
+ int crtc;
+ union callback_param cp;
+ uint32_t *crtcs = xcb_randr_get_screen_resources_crtcs( r );
+
+ for( crtc = 0; crtc < r->num_crtcs; crtc++ ) {
+ struct crtc_callback *cc = xmalloc( sizeof *cc );
+
+ cc->screen = i;
+ cc->crtc = crtcs[ crtc ];
+ cc->ok = &ok;
+ cp.p = cc;
+ handle_async_reply( sequence = xcb_randr_get_crtc_info(
+ c, crtcs[ crtc ],
+ r->config_timestamp ).sequence,
+ handle_crtc_info, cp );
+ }
+
+ free( r );
+ }
+
+ ok = TRUE;
+ sync_with_callback( sequence );
+ if( !ok ) {
+ resources_cookies[ i ] =
+ xcb_randr_get_screen_resources( c, screens[ i ]->root );
+
+ xcb_flush( c ); /* BLOCK */
+
+ goto retry_race;
+ }
+ }
+ }
+#endif
+
for( i = 0; i < num_screens; i++ ) {
xcb_generic_error_t *error = xcb_request_check( c, cookies[ i ] );
xcb_query_tree_reply_t *r;
@@ -2325,6 +2648,18 @@ static void handle_events( void ) {
break;
default:
+#if USE_RANDR
+ if( event_base_type == extension_event[ EXT_RANDR ] +
+ XCB_RANDR_NOTIFY &&
+ ( (xcb_randr_notify_event_t *) ev )->subCode ==
+ XCB_RANDR_NOTIFY_CRTC_CHANGE ) {
+ w = ( (xcb_randr_notify_event_t *) ev )->u.cc.window;
+ latest_timestamp =
+ ( (xcb_randr_notify_event_t *) ev )->u.cc.timestamp;
+ event_type = RANDR_CRTC_CHANGE_NOTIFY;
+ break;
+ }
+#endif
#if USE_SHAPE
if( event_base_type == extension_event[ EXT_SHAPE ] ) {
w = ( (xcb_shape_notify_event_t *) ev )->affected_window;
@@ -2333,7 +2668,7 @@ static void handle_events( void ) {
}
#endif
warning( "Unknown event" );
- break;
+ continue;
}
if( ev->response_type & 0x80 )
diff --git a/gwm.h b/gwm.h
index 10ade58..8d5525c 100644
--- a/gwm.h
+++ b/gwm.h
@@ -49,7 +49,9 @@ extern MALLOC void *xcalloc( size_t number, size_t size );
enum _extended_event_type {
SYNTHETIC_EVENT = XCB_MAPPING_NOTIFY + 1,
- SHAPE_NOTIFY
+ RANDR_CRTC_CHANGE_NOTIFY,
+ SHAPE_NOTIFY,
+ NUM_EXTENDED_EVENTS
};
extern xcb_connection_t *c;
@@ -124,6 +126,9 @@ enum gwm_extension {
#if USE_DAMAGE
EXT_DAMAGE,
#endif
+#if USE_RANDR
+ EXT_RANDR,
+#endif
#if USE_RENDER
EXT_RENDER,
#endif
@@ -136,7 +141,8 @@ enum gwm_extension {
NUM_EXTENSIONS
};
-#if USE_COMPOSITE || USE_DAMAGE || USE_RENDER || USE_SHAPE || USE_FIXES
+#if USE_COMPOSITE || USE_DAMAGE || USE_RANDR || USE_RENDER || USE_SHAPE || \
+ USE_XFIXES
#define EXTENSIONS_SIZE NUM_EXTENSIONS
#else
#define EXTENSIONS_SIZE 1 /* avoid zero length arrays */
@@ -166,12 +172,23 @@ enum decoration_col {
extern int num_screens;
extern xcb_screen_t **screens;
+#if USE_RANDR
+struct gwm_crtc {
+ uint32_t crtc;
+ int x, y, width, height;
+};
+#endif
+
struct gwm_screen {
xcb_visualtype_t *root_visual;
xcb_atom_t wm_atom; /* the atom WM_Sn, where n is the screen number */
xcb_colormap_t cmap; /* the most recently installed colormap */
xcb_timestamp_t cmap_time; /* the time at which it was installed */
uint32_t pixels[ NUM_COLS ]; /* pixel values in the default cmap */
+#if USE_RANDR
+ struct gwm_crtc **crtcs;
+ int num_crtcs;
+#endif
};
extern struct gwm_screen *gwm_screens;
@@ -360,6 +377,14 @@ extern CONST int initial_press( xcb_button_press_event_t *ev );
terminate a passive grab). */
extern CONST int final_release( xcb_button_release_event_t *ev );
+/* Return TRUE iff a point is "live" (within at least one CRTC). */
+extern int point_live( int screen, int x, int y );
+
+/* Move an area the shortest Manhattan distance so that at an area of at
+ least min_x x min_y pixels fall within at least one CRTC, if possible. */
+extern void make_area_live( int screen, int *x, int *y, int width, int height,
+ int min_x, int min_y );
+
extern CONST xcb_timestamp_t event_time( xcb_generic_event_t *ev );
/* The window whose event handler(s) should be invoked for pointer events.
@@ -372,6 +397,9 @@ extern xcb_window_t pointer_demux;
extern void install_window_colormap( int screen, struct gwm_window *window,
xcb_timestamp_t t );
+extern void update_crtc( int screen, int crtc, int width, int height,
+ int x, int y );
+
extern void generic_expose( struct gwm_window *window,
xcb_expose_event_t *ev );
diff --git a/managed.c b/managed.c
index 31d8ba4..a510441 100644
--- a/managed.c
+++ b/managed.c
@@ -795,7 +795,7 @@ static void managed_shape_notify( struct gwm_window *window,
}
#endif
-const event_handler managed_handlers[] = {
+const event_handler managed_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -832,6 +832,7 @@ const event_handler managed_handlers[] = {
NULL, /* ClientMessage */
NULL, /* MappingNotify */
NULL, /* (synthetic) */
+ NULL, /* RRNotify */
#if USE_SHAPE
(event_handler) managed_shape_notify
#else
diff --git a/menu.c b/menu.c
index f6fddad..a1ff648 100644
--- a/menu.c
+++ b/menu.c
@@ -162,6 +162,185 @@ static void menu_leave_notify( struct gwm_window *window,
deactivate_menu_item( window, ev );
}
+static void fit_menu( int screen, int *x, int *y, int *width, int *height ) {
+
+#if USE_RANDR
+ if( have_extension[ EXT_RANDR ] ) {
+ int i;
+ int best_x = INT_MAX >> 1, best_y = 0, best_width = 0, best_height = 0;
+
+ /* If the pointer falls outside all CRTCs, treat it as if it were
+ moved into the closest. */
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+ int cx, cy;
+
+ if( *x < c->x )
+ cx = c->x;
+ else if( *x >= c->x + c->width )
+ cx = c->x + c->width - 1;
+ else
+ cx = *x;
+
+ if( *y < c->y )
+ cy = c->y;
+ else if( *y >= c->y + c->height )
+ cy = c->y + c->height - 1;
+ else
+ cy = *y;
+
+ if( abs( cx - *x ) + abs( cy - *y ) <
+ abs( best_x - *x ) + abs( best_y - *y ) ) {
+ best_x = cx;
+ best_y = cy;
+
+ if( cx == *x && cy == *y )
+ break;
+ }
+ }
+
+ *x = best_x;
+ *y = best_y;
+
+ /* Limit the width to half that of the widest CRTC containing the
+ pointer. */
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+
+ if( *x >= c->x && *x < c->x + c->width &&
+ *y >= c->y && *y < c->y + c->height &&
+ c->width >> 1 > best_width )
+ best_width = c->width >> 1;
+ }
+
+ if( best_width < 8 )
+ best_width = 8;
+ if( best_width < *width )
+ *width = best_width;
+
+ /* Limit the height to that of the tallest CRTC containing the
+ pointer. */
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+
+ if( *x >= c->x && *x < c->x + c->width &&
+ *y >= c->y && *y < c->y + c->height && c->height > best_height )
+ best_height = c->height;
+ }
+
+ if( best_height < 8 )
+ best_height = 8;
+ if( best_height < *height )
+ *height = best_height;
+
+ /* If there exists some CRTC which contains the pointer and
+ the entire rectangle, we've finished. */
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+
+ if( *x >= c->x && *x + *width <= c->x + c->width &&
+ *y >= c->y && *y + *height <= c->y + c->height )
+ return;
+ }
+
+ /* If there exists some CRTC which contains the pointer and
+ could hold the entire rectangle if it were moved, move it
+ into one of them. */
+ best_x = INT_MAX >> 1;
+ best_y = 0;
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+
+ if( *x >= c->x && *x < c->x + c->width && *width <= c->width &&
+ *y >= c->y && *y < c->y + c->height && *height <= c->height ) {
+ int cx, cy;
+
+ if( *x < c->x )
+ cx = c->x;
+ else if( *x + *width > c->x + c->width )
+ cx = c->x + c->width - *width;
+ else
+ cx = *x;
+
+ if( *y < c->y )
+ cy = c->y;
+ else if( *y + *height > c->y + c->height )
+ cy = c->y + c->height - *height;
+ else
+ cy = *y;
+
+ if( abs( cx - *x ) + abs( cy - *y ) <
+ abs( best_x - *x ) + abs( best_y - *y ) ) {
+ best_x = cx;
+ best_y = cy;
+
+ if( cx == *x && cy == *y )
+ break;
+ }
+ }
+ }
+
+ if( best_x < INT_MAX >> 1 ) {
+ *x = best_x;
+ *y = best_y;
+
+ return;
+ }
+
+ /* We can't do a good job, no matter what we try. Find the
+ CRTC which will hold as much of the rectangle as possible,
+ and move it there. Prefer to show the top and the left. */
+ best_width = 0;
+ for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
+ struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
+ int cw = ( c->width > *width ? *width : c->width ) +
+ ( c->height > *height ? *height : c->height );
+
+ if( cw > best_width ) {
+ best_width = cw;
+
+ if( *x + *width > c->x + c->width )
+ best_x = c->x + c->width - *width;
+ else
+ best_x = *x;
+
+ if( best_x < c->x )
+ best_x = c->x;
+
+ if( *y + *height > c->y + c->height )
+ best_y = c->y + c->height - *height;
+ else
+ best_y = *y;
+
+ if( best_y < c->y )
+ best_y = c->y;
+ }
+ }
+
+ *x = best_x;
+ *y = best_y;
+
+ return;
+ }
+#endif
+
+ if( *width > screens[ screen ]->width_in_pixels >> 1 )
+ *width = screens[ screen ]->width_in_pixels >> 1;
+
+ if( *height > screens[ screen ]->height_in_pixels )
+ *height = screens[ screen ]->height_in_pixels;
+
+ if( *x + *width > screens[ screen ]->width_in_pixels )
+ *x = screens[ screen ]->width_in_pixels - *width;
+ if( *x < 0 )
+ *x = 0;
+
+ if( *y + *height > screens[ screen ]->height_in_pixels )
+ *y = screens[ screen ]->height_in_pixels - *height;
+ if( *y < 0 )
+ *y = 0;
+}
+
extern void popup_menu( struct gwm_window *window, xcb_generic_event_t *ev,
int num_items, const struct menuitem *items ) {
@@ -228,21 +407,11 @@ extern void popup_menu( struct gwm_window *window, xcb_generic_event_t *ev,
height += heights[ i ];
}
- if( menu->u.menu.width > screens[ screen ]->width_in_pixels >> 1 )
- menu->u.menu.width = screens[ screen ]->width_in_pixels >> 1;
-
- if( height > screens[ screen ]->height_in_pixels )
- height = screens[ screen ]->height_in_pixels;
-
- if( ev_x + menu->u.menu.width > screens[ screen ]->width_in_pixels )
- ev_x = screens[ screen ]->width_in_pixels - menu->u.menu.width;
- if( ev_x < 0 )
- ev_x = 0;
-
- if( ev_y + height > screens[ screen ]->height_in_pixels )
- ev_y = screens[ screen ]->height_in_pixels - height;
- if( ev_y < 0 )
- ev_y = 0;
+ menu->u.menu.width += 2; /* account for border */
+ height += 2;
+ fit_menu( screen, &ev_x, &ev_y, &menu->u.menu.width, &height );
+ menu->u.menu.width -= 2;
+ height -= 2;
values[ 0 ] = gwm_screens[ screen ].pixels[ COL_BORDER ]; /* border pixel */
values[ 1 ] = TRUE; /* override redirect */
@@ -275,7 +444,7 @@ extern void popup_menu( struct gwm_window *window, xcb_generic_event_t *ev,
pointer_demux = menu->w;
}
-const event_handler menu_handlers[] = {
+const event_handler menu_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -312,8 +481,9 @@ const event_handler menu_handlers[] = {
NULL, /* ClientMessage */
NULL, /* MappingNotify */
NULL, /* (synthetic) */
- NULL /* ShapeNotify */
-}, menuitem_handlers[] = {
+ NULL, /* RRNotify */
+ NULL /* ShapeNotify */
+}, menuitem_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
NULL, /* KeyPress */
@@ -350,5 +520,6 @@ const event_handler menu_handlers[] = {
NULL, /* ClientMessage */
NULL, /* MappingNotify */
NULL, /* (synthetic) */
- NULL /* ShapeNotify */
+ NULL, /* RRNotify */
+ NULL /* ShapeNotify */
};
diff --git a/root.c b/root.c
index 14ece34..83176c1 100644
--- a/root.c
+++ b/root.c
@@ -23,6 +23,9 @@
#include <config.h>
+#if USE_RANDR
+#include <xcb/randr.h>
+#endif
#include <xcb/xcb.h>
#include "gwm.h"
@@ -147,8 +150,35 @@ static void root_reparent_notify( struct gwm_window *window,
static void root_configure_notify( struct gwm_window *window,
xcb_configure_notify_event_t *ev ) {
- stack_move_above( &window_stack, ev->window, ev->above_sibling ?
- ev->above_sibling : window->w | STACK_END );
+ if( ev->event == ev->window ) {
+ /* The root window itself was configured. */
+ if( screens[ window->screen ]->width_in_pixels !=
+ ev->width ||
+ screens[ window->screen ]->height_in_pixels !=
+ ev->height ) {
+ uint32_t values[ 4 ];
+
+ screens[ window->screen ]->width_in_pixels = ev->width;
+ screens[ window->screen ]->height_in_pixels = ev->height;
+
+ values[ 0 ] = ev->width;
+ values[ 1 ] = ev->height;
+ xcb_change_property( c, XCB_PROP_MODE_REPLACE, ev->window,
+ atoms[ ATOM__NET_DESKTOP_GEOMETRY ], CARDINAL,
+ 32, 2, values );
+
+ values[ 0 ] = 0;
+ values[ 1 ] = 0;
+ values[ 2 ] = ev->width;
+ values[ 3 ] = ev->height;
+ xcb_change_property( c, XCB_PROP_MODE_REPLACE, ev->window,
+ atoms[ ATOM__NET_WORKAREA ], CARDINAL, 32, 4,
+ values );
+ }
+ } else
+ /* A child of the root was configured. */
+ stack_move_above( &window_stack, ev->window, ev->above_sibling ?
+ ev->above_sibling : window->w | STACK_END );
}
extern void root_circulate_request( struct gwm_window *window,
@@ -210,7 +240,21 @@ static void root_synthetic( struct gwm_window *root,
}
}
-const event_handler root_handlers[] = {
+#if USE_RANDR
+static void root_rr_crtc_change_notify( struct gwm_window *window,
+ xcb_randr_notify_event_t *ev ) {
+
+ int rotated = ev->u.cc.rotation & ( XCB_RANDR_ROTATION_ROTATE_90 |
+ XCB_RANDR_ROTATION_ROTATE_270 );
+
+ update_crtc( window->screen, ev->u.cc.crtc,
+ rotated ? ev->u.cc.height : ev->u.cc.width,
+ rotated ? ev->u.cc.width : ev->u.cc.height,
+ ev->u.cc.x, ev->u.cc.y );
+}
+#endif
+
+const event_handler root_handlers[ NUM_EXTENDED_EVENTS ] = {
NULL, /* Error */
NULL, /* Reply */
(event_handler) root_key_press,
@@ -247,5 +291,10 @@ const event_handler root_handlers[] = {
NULL, /* ClientMessage */
NULL, /* MappingNotify */
(event_handler) root_synthetic,
- NULL /* ShapeNotify */
+#if USE_RANDR
+ (event_handler) root_rr_crtc_change_notify, /* RRCrtcChangeNotify */
+#else
+ NULL, /* RRCrtcChangeNotify */
+#endif
+ NULL /* ShapeNotify */
};