/* * frame.c * * Part of gwm, the Gratuitous Window Manager, * by Gary Wong, . * * Copyright (C) 2009 Gary Wong * * This program is free software: you can redistribute it and/or modify * it under the terms of version 3 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * $Id$ */ #include #include #include #include #include "gwm.h" #include "actions.h" #include "frame.h" #include "managed.h" #include "window-table.h" #define EDGE_RESIST 8 #define FRAME_X_BORDER 1 /* X border added to all four sides of frame */ #define FRAME_BORDER_WIDTH 2 /* pixels added to left, right and bottom; includes padding but excludes X border */ #define FRAME_TITLE_HEIGHT 16 /* pixels added to top; includes title bar but excludes X border */ extern int frame_t( struct gwm_window *window, int include_x_border ) { int base, border; assert( window->type == WINDOW_FRAME ); if( window->u.frame.decoration & DEC_BORDER ) { base = FRAME_BORDER_WIDTH; border = FRAME_X_BORDER; } else base = border = 0; if( window->u.frame.decoration & DEC_TITLE ) base = FRAME_TITLE_HEIGHT; return base + ( include_x_border ? border : 0 ); } static int frame_blr( struct gwm_window *window, int include_x_border ) { int base, border; assert( window->type == WINDOW_FRAME ); if( window->u.frame.decoration & DEC_BORDER ) { base = FRAME_BORDER_WIDTH; border = FRAME_X_BORDER; } else base = border = 0; return base + ( include_x_border ? border : 0 ); } extern int frame_b( struct gwm_window *window, int include_x_border ) { return frame_blr( window, include_x_border ); } extern int frame_l( struct gwm_window *window, int include_x_border ) { return frame_blr( window, include_x_border ); } extern int frame_r( struct gwm_window *window, int include_x_border ) { return frame_blr( window, include_x_border ); } extern int frame_xb( struct gwm_window *window ) { assert( window->type == WINDOW_FRAME ); return window->u.frame.decoration & DEC_BORDER ? FRAME_X_BORDER : 0; } extern void update_frame_extents( struct gwm_window *window ) { uint32_t extents[ 4 ]; extents[ 0 ] = frame_l( window, TRUE ); extents[ 1 ] = frame_r( window, TRUE ); extents[ 2 ] = frame_t( window, TRUE ); extents[ 3 ] = frame_b( window, TRUE ); xcb_change_property( c, XCB_PROP_MODE_REPLACE, window->u.frame.child->w, atoms[ ATOM__NET_FRAME_EXTENTS ], CARDINAL, 32, 4, extents ); } extern void translate_child_to_frame( struct gwm_window *frame, int *fx, int *fy, int *fwidth, int *fheight, int cx, int cy, int cwidth, int cheight, int cborder, int win_gravity ) { *fwidth = cwidth + frame_l( frame, FALSE ) + frame_r( frame, FALSE ); *fheight = cheight + frame_t( frame, FALSE ) + frame_b( frame, FALSE ); switch( win_gravity ) { case XCB_GRAVITY_NORTH_WEST: case XCB_GRAVITY_WEST: case XCB_GRAVITY_SOUTH_WEST: default: *fx = cx; break; case XCB_GRAVITY_NORTH: case XCB_GRAVITY_CENTER: case XCB_GRAVITY_SOUTH: *fx = cx + cborder - ( ( frame_l( frame, TRUE ) + frame_r( frame, TRUE ) ) >> 1 ); break; case XCB_GRAVITY_NORTH_EAST: case XCB_GRAVITY_EAST: case XCB_GRAVITY_SOUTH_EAST: *fx = cx + ( cborder << 1 ) - frame_l( frame, TRUE ) - frame_r( frame, TRUE ); break; case XCB_GRAVITY_STATIC: *fx = cx + cborder - frame_l( frame, TRUE ); } switch( win_gravity ) { case XCB_GRAVITY_NORTH_WEST: case XCB_GRAVITY_NORTH: case XCB_GRAVITY_NORTH_EAST: default: *fy = cy; break; case XCB_GRAVITY_WEST: case XCB_GRAVITY_CENTER: case XCB_GRAVITY_EAST: *fy = cy + cborder - ( ( frame_t( frame, TRUE ) + frame_b( frame, TRUE ) ) >> 1 ); break; case XCB_GRAVITY_SOUTH_WEST: case XCB_GRAVITY_SOUTH: case XCB_GRAVITY_SOUTH_EAST: *fy = cy + ( cborder << 1 ) - frame_t( frame, TRUE ) - frame_b( frame, TRUE ); break; case XCB_GRAVITY_STATIC: *fy = cy + cborder - frame_t( frame, TRUE ); break; } } extern void translate_frame_to_child( struct gwm_window *frame, int *cx, int *cy, int fx, int fy, int cborder, int win_gravity ) { switch( win_gravity ) { case XCB_GRAVITY_NORTH_WEST: case XCB_GRAVITY_WEST: case XCB_GRAVITY_SOUTH_WEST: default: *cx = fx; break; case XCB_GRAVITY_NORTH: case XCB_GRAVITY_CENTER: case XCB_GRAVITY_SOUTH: *cx = fx - cborder + ( ( frame_l( frame, TRUE ) + frame_r( frame, TRUE ) ) >> 1 ); break; case XCB_GRAVITY_NORTH_EAST: case XCB_GRAVITY_EAST: case XCB_GRAVITY_SOUTH_EAST: *cx = fx - ( cborder << 1 ) + frame_l( frame, TRUE ) + frame_r( frame, TRUE ); break; case XCB_GRAVITY_STATIC: *cx = fx - cborder + frame_l( frame, TRUE ); break; } switch( win_gravity ) { case XCB_GRAVITY_NORTH_WEST: case XCB_GRAVITY_NORTH: case XCB_GRAVITY_NORTH_EAST: default: *cy = fy; break; case XCB_GRAVITY_WEST: case XCB_GRAVITY_CENTER: case XCB_GRAVITY_EAST: *cy = fy - cborder + ( ( frame_t( frame, TRUE ) + frame_b( frame, TRUE ) ) >> 1 ); break; case XCB_GRAVITY_SOUTH_WEST: case XCB_GRAVITY_SOUTH: case XCB_GRAVITY_SOUTH_EAST: *cy = fy - ( cborder << 1 ) + frame_t( frame, TRUE ) + frame_b( frame, TRUE ); break; case XCB_GRAVITY_STATIC: *cy = fy - cborder + frame_t( frame, TRUE ); break; } } extern void apply_size_constraints( struct gwm_window *window, int *width, int *height ) { int eff_base_width = window->u.managed.base_width ? window->u.managed.base_width : window->u.managed.min_width, eff_base_height = window->u.managed.base_height ? window->u.managed.base_height : window->u.managed.min_height; /* Apply the minimum and maximum constraints. These are already known to be compatible. */ if( *width < window->u.managed.min_width ) *width = window->u.managed.min_width; if( *height < window->u.managed.min_height ) *height = window->u.managed.min_height; if( *width > window->u.managed.max_width ) *width = window->u.managed.max_width; if( *height > window->u.managed.max_height ) *height = window->u.managed.max_height; /* Now round down each dimension to an integer multiple of increments. Rounding down cannot violate the maximum constraint, and since eff_base_* >= min_*, it will not reduce below the minimum constraint. */ *width -= ( *width - eff_base_width ) % window->u.managed.width_inc; *height -= ( *height - eff_base_height ) % window->u.managed.height_inc; if( window->u.managed.min_aspect_x * *height > window->u.managed.min_aspect_y * *width ) { /* Minimum aspect ratio violated. Attempt to either increase the width or decrease the height (whichever is a smaller change), but don't do either if it would go outside the min/max bounds. Both division operations are safe (min_aspect_y is always positive, and min_aspect_x must be positive if there is a violation). Note that an exact solution might not be possible (e.g. certain cases where the aspect ratio and increments are coprime). */ int min_x, max_y; min_x = ( window->u.managed.min_aspect_x * *height + ( window->u.managed.min_aspect_y - 1 ) ) / window->u.managed.min_aspect_y + window->u.managed.width_inc - 1; min_x -= ( min_x - eff_base_width ) % window->u.managed.width_inc; max_y = window->u.managed.min_aspect_y * *width / window->u.managed.min_aspect_x; max_y -= ( max_y - eff_base_height ) % window->u.managed.height_inc; if( min_x - *width < *height - max_y ) { /* The width change is smaller: prefer it if possible. */ if( min_x >= window->u.managed.min_width ) *width = min_x; else if( max_y < window->u.managed.max_height ) *height = max_y; } else { /* The height change is smaller: prefer it if possible. */ if( max_y < window->u.managed.max_height ) *height = max_y; else if( min_x >= window->u.managed.min_width ) *width = min_x; } } if( window->u.managed.max_aspect_x * *height < window->u.managed.max_aspect_y * *width ) { /* Maximum aspect ratio violated. Much like the case above... */ int min_y, max_x; min_y = ( window->u.managed.max_aspect_y * *width + ( window->u.managed.max_aspect_x - 1 ) ) / window->u.managed.max_aspect_x + window->u.managed.height_inc - 1; min_y -= ( min_y - eff_base_height ) % window->u.managed.height_inc; max_x = window->u.managed.max_aspect_x * *height / window->u.managed.max_aspect_y; max_x -= ( max_x - eff_base_width ) % window->u.managed.width_inc; if( min_y - *height < *width - max_x ) { /* The height change is smaller: prefer it if possible. */ if( min_y >= window->u.managed.min_height ) *height = min_y; else if( max_x < window->u.managed.max_width ) *width = max_x; } else { /* The width change is smaller: prefer it if possible. */ if( max_x < window->u.managed.max_width ) *width = max_x; else if( min_y >= window->u.managed.min_height ) *height = min_y; } } } struct gwm_window *focus_frame; extern void activate_focus_frame( xcb_timestamp_t time ) { if( focus_frame->u.frame.decoration & ( DEC_TITLE | DEC_BORDER ) ) { uint32_t n; n = gwm_screens[ focus_frame->screen ].pixels[ COL_FRAME_ACTIVE ]; xcb_change_window_attributes( c, focus_frame->w, XCB_CW_BACK_PIXEL, &n ); queue_window_update( focus_frame, 0, 0, focus_frame->u.frame.width, focus_frame->u.frame.height, FALSE ); } if( focus_frame->u.frame.child->u.managed.hints & HINT_INPUT ) /* Give the client the focus (ICCCM 2.0, section 4.1.7). */ /* Ignore Window errors (which can occur if the client destroys the window before our request arrives) and Match errors (which can occur if the window is unmapped first). */ handle_error_reply( xcb_set_input_focus_checked( c, XCB_INPUT_FOCUS_POINTER_ROOT, focus_frame->u.frame.child->w, time ), ERR_MASK_WINDOW | ERR_MASK_MATCH ); if( focus_frame->u.frame.child->u.managed.protocols & PROTOCOL_TAKE_FOCUS ) { /* Tell the client to take the focus (ICCCM 2.0, section 4.1.7). */ xcb_client_message_event_t msg; msg.response_type = XCB_CLIENT_MESSAGE; msg.format = 32; msg.sequence = 0; msg.window = focus_frame->u.frame.child->w; msg.type = atoms[ ATOM_WM_PROTOCOLS ]; msg.data.data32[ 0 ] = atoms[ ATOM_WM_TAKE_FOCUS ]; msg.data.data32[ 1 ] = time; msg.data.data32[ 2 ] = 0; msg.data.data32[ 3 ] = 0; msg.data.data32[ 4 ] = 0; handle_error_reply( xcb_send_event_checked( c, FALSE, focus_frame->u.frame.child->w, 0, (char *) &msg ), ERR_MASK_WINDOW ); } if( !( focus_frame->u.frame.child->u.managed.hints & HINT_INPUT ) && !( focus_frame->u.frame.child->u.managed.protocols & PROTOCOL_TAKE_FOCUS ) ) /* The client really doesn't want the focus. */ xcb_set_input_focus( c, XCB_INPUT_FOCUS_NONE, XCB_INPUT_FOCUS_POINTER_ROOT, time ); install_window_colormap( focus_frame->screen, focus_frame->u.frame.child, time ); } extern void deactivate_focus_frame( void ) { uint32_t n; if( !focus_frame ) return; if( focus_frame->u.frame.decoration & ( DEC_TITLE | DEC_BORDER ) ) { n = gwm_screens[ focus_frame->screen ].pixels[ COL_FRAME_INACTIVE ]; xcb_change_window_attributes( c, focus_frame->w, XCB_CW_BACK_PIXEL, &n ); } if( focus_frame->u.frame.decoration & DEC_TITLE ) queue_window_update( focus_frame, 0, 0, focus_frame->u.frame.width, focus_frame->u.frame.height, FALSE ); } static enum _window_operation { OP_NONE, OP_MOVE, OP_RESIZE } window_op; static enum size_which { SIZE_LESSER, SIZE_NONE, SIZE_GREATER } size_x, size_y; static int dx, dy, init_x, init_y, moved; static struct gwm_window *feedback; static struct h_edge { int x_min, x_max; int y; } *t_edges, *b_edges; static struct v_edge { int x; int y_min, y_max; } *l_edges, *r_edges; static int num_t_edges, num_b_edges, num_l_edges, num_r_edges; static int h_edge_compare( const void *v0, const void *v1 ) { const struct h_edge *e0 = v0, *e1 = v1; return e0->y - e1->y; } static int v_edge_compare( const void *v0, const void *v1 ) { const struct v_edge *e0 = v0, *e1 = v1; return e0->x - e1->x; } 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 ); #if USE_RANDR if( have_extension[ EXT_RANDR ] ) num_crtcs = gwm_screens[ screen ].num_crtcs; else #endif num_crtcs = 1; 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 = num_crtcs; end = windows.values + windows.used; for( windowp = windows.values; windowp < end; windowp++ ) if( ( window = *windowp )->type == WINDOW_FRAME && window->screen == screen ) { int t = window->u.frame.y, b = window->u.frame.y + window->u.frame.height + ( frame_xb( window ) << 1 ), l = window->u.frame.x, r = window->u.frame.x + window->u.frame.width + ( frame_xb( window ) << 1 ); if( t >= screens[ screen ]->height_in_pixels || b <= 0 || l >= screens[ screen ]->width_in_pixels || r <= 0 ) continue; /* window is entirely off screen; ignore */ if( t >= 0 ) { t_edges[ num_t_edges ].x_min = l; t_edges[ num_t_edges ].x_max = r; t_edges[ num_t_edges ].y = t; num_t_edges++; } if( b <= screens[ screen ]->height_in_pixels ) { b_edges[ num_b_edges ].x_min = l; b_edges[ num_b_edges ].x_max = r; b_edges[ num_b_edges ].y = b; num_b_edges++; } if( l >= 0 ) { l_edges[ num_l_edges ].x = l; l_edges[ num_l_edges ].y_min = t; l_edges[ num_l_edges ].y_max = b; num_l_edges++; } if( r <= screens[ screen ]->width_in_pixels ) { r_edges[ num_r_edges ].x = r; r_edges[ num_r_edges ].y_min = t; r_edges[ num_r_edges ].y_max = b; num_r_edges++; } } qsort( t_edges, num_t_edges, sizeof *t_edges, h_edge_compare ); qsort( b_edges, num_b_edges, sizeof *b_edges, h_edge_compare ); qsort( l_edges, num_l_edges, sizeof *l_edges, v_edge_compare ); qsort( r_edges, num_r_edges, sizeof *r_edges, v_edge_compare ); } static void free_edges( void ) { assert( t_edges ); assert( b_edges ); assert( l_edges ); assert( r_edges ); free( t_edges ); t_edges = NULL; free( b_edges ); b_edges = NULL; free( l_edges ); l_edges = NULL; free( r_edges ); r_edges = NULL; } static void edge_resist( int old_t, int old_b, int old_l, int old_r, int new_t, int new_b, int new_l, int new_r, int *ex, int *ey ) { *ex = *ey = 0; if( new_t < old_t ) { /* Trying to move up; look for bottom edges with new_t < y <= new_t + EDGE_RESIST. */ int i0 = 0, i1 = num_b_edges - 1, i; while( i1 > i0 + 1 ) { int i_mid = ( i0 + i1 ) >> 1; if( b_edges[ i_mid ].y <= new_t ) i0 = i_mid; else i1 = i_mid; } while( i0 < num_b_edges && b_edges[ i0 ].y <= new_t ) i0++; for( i = i0; i < num_b_edges && b_edges[ i ].y <= new_t + EDGE_RESIST; i++ ) if( b_edges[ i ].x_min < new_r && b_edges[ i ].x_max > new_l ) *ey = b_edges[ i ].y - new_t; } else if( new_b > old_b ) { /* Trying to move down; look for top edges with new_b > y >= new_b - EDGE_RESIST. */ int i0 = 0, i1 = num_t_edges - 1, i; while( i1 > i0 + 1 ) { int i_mid = ( i0 + i1 ) >> 1; if( t_edges[ i_mid ].y >= new_b ) i1 = i_mid; else i0 = i_mid; } while( i1 >= 0 && t_edges[ i1 ].y >= new_b ) i1--; for( i = i1; i >= 0 && t_edges[ i ].y >= new_b - EDGE_RESIST; i-- ) if( t_edges[ i ].x_min < new_r && t_edges[ i ].x_max > new_l ) *ey = t_edges[ i ].y - new_b; } if( new_l < old_l ) { /* Trying to move left; look for right edges with new_l < x <= new_l + EDGE_RESIST. */ int i0 = 0, i1 = num_r_edges - 1, i; while( i1 > i0 + 1 ) { int i_mid = ( i0 + i1 ) >> 1; if( r_edges[ i_mid ].x <= new_l ) i0 = i_mid; else i1 = i_mid; } while( i0 < num_r_edges && r_edges[ i0 ].x <= new_l ) i0++; for( i = i0; i < num_r_edges && r_edges[ i ].x <= new_l + EDGE_RESIST; i++ ) if( r_edges[ i ].y_min < new_b && r_edges[ i ].y_max > new_t ) *ex = r_edges[ i ].x - new_l; } else if( new_r > old_r ) { /* Trying to move right; look for left edges with new_r > x >= new_r - EDGE_RESIST. */ int i0 = 0, i1 = num_l_edges - 1, i; while( i1 > i0 + 1 ) { int i_mid = ( i0 + i1 ) >> 1; if( l_edges[ i_mid ].x >= new_r ) i1 = i_mid; else i0 = i_mid; } while( i1 >= 0 && l_edges[ i1 ].x >= new_r ) i1--; for( i = i1; i >= 0 && l_edges[ i ].x >= new_r - EDGE_RESIST; i-- ) if( l_edges[ i ].y_min < new_b && l_edges[ i ].y_max > new_t ) *ex = l_edges[ i ].x - new_r; } } static void recalc_size( struct gwm_window *window, int x, int y, int apply_edge_resist, xcb_timestamp_t t ) { int new_width, new_height, child_width, child_height; enum size_which old_size_x = size_x, old_size_y = size_y; int old_t, old_b, old_l, old_r, new_t, new_b, new_l, new_r; old_t = window->u.frame.y; old_b = old_t + window->u.frame.height + ( frame_xb( window ) << 1 ); old_l = window->u.frame.x; old_r = old_l + window->u.frame.width + ( frame_xb( window ) << 1 ); if( size_x == SIZE_LESSER ) { new_l = x - dx; new_r = old_r; } else if( size_x == SIZE_GREATER ) { new_l = old_l; new_r = x - dx; } else if( x < window->u.frame.x + ( window->u.frame.width >> 2 ) ) { /* Start sizing left border. */ size_x = SIZE_LESSER; new_l = old_l; new_r = old_r; dx = x - old_l; } else if( x > window->u.frame.x + window->u.frame.width - ( window->u.frame.width >> 2 ) ) { /* Start sizing right border. */ size_x = SIZE_GREATER; new_l = old_l; new_r = old_r; dx = x - old_r; } else { new_l = old_l; new_r = old_r; } if( size_y == SIZE_LESSER ) { new_t = y - dy; new_b = old_b; } else if( size_y == SIZE_GREATER ) { new_t = old_t; new_b = y - dy; } else if( y < window->u.frame.y + ( window->u.frame.height >> 2 ) ) { /* Start sizing top border. */ size_y = SIZE_LESSER; new_t = old_t; new_b = old_b; dy = y - old_t; } else if( y > window->u.frame.y + window->u.frame.height - ( window->u.frame.height >> 2 ) ) { /* Start sizing bottom border. */ size_y = SIZE_GREATER; new_t = old_t; new_b = old_b; dy = y - old_b; } else { new_t = old_t; new_b = old_b; } if( size_x != old_size_x || size_y != old_size_y ) xcb_change_active_pointer_grab( c, cursors[ CURSOR_TL + size_y * 3 + size_x ], t, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION_HINT | XCB_EVENT_MASK_BUTTON_MOTION ); if( apply_edge_resist ) { int ex, ey; edge_resist( old_t, old_b, old_l, old_r, new_t, new_b, new_l, new_r, &ex, &ey ); if( ex > 0 ) new_l += ex; else new_r += ex; if( ey > 0 ) new_t += ey; else new_b += ey; } new_width = new_r - new_l - ( frame_xb( window ) << 1 ); new_height = new_b - new_t - ( frame_xb( window ) << 1 ); child_width = new_width - frame_l( window, FALSE ) - frame_r( window, FALSE ); child_height = new_height - frame_t( window, FALSE ) - frame_b( window, FALSE ); apply_size_constraints( window->u.frame.child, &child_width, &child_height ); new_width = child_width + frame_l( window, FALSE ) + frame_r( window, FALSE ); new_height = child_height + frame_t( window, FALSE ) + frame_b( window, FALSE ); if( size_x == SIZE_LESSER ) new_l = new_r - new_width - ( frame_xb( window ) << 1 ); if( size_y == SIZE_LESSER ) new_t = new_b - new_height - ( frame_xb( window ) << 1 ); if( new_l != window->u.frame.x || new_t != window->u.frame.y || new_width != window->u.frame.width || new_height != window->u.frame.height ) { int disp_width, disp_height; int new_fb_width, new_fb_height; uint32_t values[ 4 ]; values[ 0 ] = new_l; values[ 1 ] = new_t; values[ 2 ] = new_width; values[ 3 ] = new_height; xcb_configure_window( c, window->w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values ); values[ 0 ] = new_width - frame_l( window, FALSE ) - frame_r( window, FALSE ); values[ 1 ] = new_height - frame_t( window, FALSE ) - frame_b( window, FALSE ); /* FIXME See _NET_WM_SYNC_REQUEST in the EWMH to avoid resizing the window faster than the client can redraw it. */ xcb_configure_window( c, window->u.frame.child->w, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values ); if( feedback ) window_size( feedback, &disp_width, &disp_height ); else { uint32_t values[ 4 ]; feedback = add_window( xcb_generate_id( c ) ); feedback->screen = window->screen; feedback->type = WINDOW_FEEDBACK; feedback->u.feedback.fb_width = feedback->u.feedback.fb_height = -1; window_size( feedback, &disp_width, &disp_height ); values[ 0 ] = gwm_screens[ window->screen ].pixels[ COL_FEEDBACK_BACK ]; /* background pixel */ values[ 1 ] = gwm_screens[ window->screen ].pixels[ COL_BORDER ]; /* border pixel */ values[ 2 ] = TRUE; /* override redirect */ values[ 3 ] = XCB_EVENT_MASK_EXPOSURE; xcb_create_window( c, XCB_COPY_FROM_PARENT, feedback->w, screens[ window->screen ]->root, ( screens[ window->screen ]->width_in_pixels - disp_width - 2 ) >> 1, ( screens[ window->screen ]->height_in_pixels - disp_height - 2 ) >> 1, disp_width, disp_height, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, values ); xcb_map_window( c, feedback->w ); } new_fb_width = ( values[ 0 ] - window->u.frame.child->u.managed.base_width ) / window->u.frame.child->u.managed.width_inc; new_fb_height = ( values[ 1 ] - window->u.frame.child->u.managed.base_height ) / window->u.frame.child->u.managed.height_inc; if( new_fb_width != feedback->u.feedback.fb_width || new_fb_height != feedback->u.feedback.fb_height ) { feedback->u.feedback.fb_width = new_fb_width; feedback->u.feedback.fb_height = new_fb_height; queue_window_update( feedback, 0, 0, disp_width, disp_height, FALSE ); } } } static void frame_button_press( struct gwm_window *window, xcb_button_press_event_t *ev ) { if( pointer_demux || ev->child == window->u.frame.child->w ) return; if( ev->detail == 2 ) { union callback_param cp; cp.p = NULL; action_window_menu( window, (xcb_generic_event_t *) ev, cp ); return; } pointer_demux = window->w; window_op = ( ev->detail > 1 ) == ( ( window->u.frame.decoration & DEC_TITLE ) && ev->event_y < frame_t( window, FALSE ) ) ? OP_RESIZE : OP_MOVE; init_x = ev->root_x; init_y = ev->root_y; if( window_op == OP_RESIZE ) { size_x = size_y = SIZE_NONE; recalc_size( window, ev->root_x, ev->root_y, FALSE, ev->time ); } else { dx = ev->event_x; dy = ev->event_y; xcb_change_active_pointer_grab( c, cursors[ CURSOR_C ], ev->time, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION_HINT | XCB_EVENT_MASK_BUTTON_MOTION ); } moved = FALSE; } static void frame_motion_notify( struct gwm_window *window, xcb_motion_notify_event_t *ev ) { uint32_t values[ 2 ]; if( !pointer_demux ) return; /* Hint to the server that we're ready for further motion events. */ xcb_query_pointer_unchecked( c, window->w ); if( !ev->same_screen ) return; if( !moved ) { /* Ignore single pixel movements. */ if( abs( ev->root_x - init_x ) > 1 || abs( ev->root_y - init_y ) > 1 ) { moved = TRUE; build_edges( window->screen ); } 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; case OP_MOVE: old_t = window->u.frame.y; old_b = old_t + window->u.frame.height + ( frame_xb( window ) << 1 ); old_l = window->u.frame.x; old_r = old_l + window->u.frame.width + ( frame_xb( window ) << 1 ); new_t = ev->root_y - dy - frame_xb( window ); new_b = new_t + window->u.frame.height + ( frame_xb( window ) << 1 ); new_l = ev->root_x - dx - frame_xb( window ); new_r = new_l + window->u.frame.width + ( frame_xb( window ) << 1 ); if( !( ev->state & XCB_MOD_MASK_CONTROL ) ) { int ex, ey; edge_resist( old_t, old_b, old_l, old_r, new_t, new_b, new_l, new_r, &ex, &ey ); new_t += ey; new_b += ey; new_l += ex; new_r += ex; } if( new_t != old_t || new_l != old_l ) { values[ 0 ] = new_l; values[ 1 ] = new_t; xcb_configure_window( c, window->w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values ); /* We're supposed to send the client a synthetic ConfigureNotify, but if we actually moved the window, then we'll do that from our own ConfigureNotify handler. */ } break; case OP_RESIZE: recalc_size( window, ev->root_x, ev->root_y, !( ev->state & XCB_MOD_MASK_CONTROL ) && window->u.frame.child->u.managed.width_inc == 1 && window->u.frame.child->u.managed.height_inc == 1, ev->time ); break; default: break; } } static void frame_button_release( struct gwm_window *window, xcb_button_release_event_t *ev ) { if( !final_release( ev ) || !pointer_demux ) return; if( moved ) free_edges(); else if( window_op ) { uint32_t n = XCB_STACK_MODE_OPPOSITE; xcb_configure_window( c, window->w, XCB_CONFIG_WINDOW_STACK_MODE, &n ); } window_op = OP_NONE; if( feedback ) { xcb_destroy_window( c, feedback->w ); forget_window( feedback ); feedback = NULL; } pointer_demux = XCB_NONE; } static void frame_enter_notify( struct gwm_window *window, xcb_enter_notify_event_t *ev ) { if( pointer_demux || focus_frame == window ) /* We have the focus already -- probably an inferior change or ungrab. */ return; deactivate_focus_frame(); focus_frame = window; activate_focus_frame( ev->time ); } static void frame_destroy_notify( struct gwm_window *window, xcb_destroy_notify_event_t *ev ) { if( window->w != pointer_demux ) return; if( moved ) free_edges(); if( feedback ) { xcb_destroy_window( c, feedback->w ); forget_window( feedback ); feedback = NULL; } } static void frame_map_request( struct gwm_window *window, xcb_map_request_event_t *ev ) { if( ev->window == window->u.frame.child->w ) /* A transition to the Normal state (ICCCM 2.0, section 4.1.4). */ iconic_to_normal( window->u.frame.child ); } extern void synthetic_configure_notify( struct gwm_window *window ) { xcb_configure_notify_event_t msg; assert( window->type == WINDOW_FRAME ); /* Send a synthetic ConfigureNotify (ICCCM 2.0, section 4.2.3). */ msg.response_type = XCB_CONFIGURE_NOTIFY; msg.event = msg.window = window->u.frame.child->w; msg.above_sibling = XCB_NONE; msg.x = window->u.frame.x + frame_l( window, TRUE ) - window->u.frame.child->u.managed.border_width; msg.y = window->u.frame.y + frame_t( window, TRUE ) - window->u.frame.child->u.managed.border_width; msg.width = window->u.frame.width - frame_l( window, FALSE ) - frame_r( window, FALSE ); msg.height = window->u.frame.height - frame_t( window, FALSE ) - frame_b( window, FALSE ); msg.border_width = window->u.frame.child->u.managed.border_width; msg.override_redirect = FALSE; handle_error_reply( xcb_send_event_checked( c, FALSE, window->u.frame.child->w, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *) &msg ), ERR_MASK_WINDOW ); } static void frame_configure_notify( struct gwm_window *window, xcb_configure_notify_event_t *ev ) { int i; if( ev->window != window->w ) return; window->u.frame.x = ev->x; window->u.frame.y = ev->y; window->u.frame.width = ev->width; window->u.frame.height = ev->height; for( i = 0; i < NUM_BORDER_REGIONS; i++ ) { uint32_t values[ 4 ]; /* x, y, width and height respectively */ switch( i ) { case BR_TL: case BR_L: case BR_BL: values[ 0 ] = 0; values[ 2 ] = window->u.frame.width >> 2; break; case BR_T: case BR_B: values[ 0 ] = window->u.frame.width >> 2; values[ 2 ] = window->u.frame.width - ( values[ 0 ] << 1 ); break; case BR_TR: case BR_R: case BR_BR: values[ 2 ] = window->u.frame.width >> 2; values[ 0 ] = window->u.frame.width - values[ 2 ]; break; } switch( i ) { case BR_TL: case BR_T: case BR_TR: if( window->u.frame.decoration & DEC_TITLE ) { values[ 1 ] = frame_t( window, FALSE ); values[ 3 ] = ( window->u.frame.height >> 2 ) - values[ 1 ]; } else { values[ 1 ] = 0; values[ 3 ] = window->u.frame.height >> 2; } break; case BR_L: case BR_R: values[ 1 ] = window->u.frame.height >> 2; values[ 3 ] = window->u.frame.height - ( values[ 1 ] << 1 ); break; case BR_BL: case BR_B: case BR_BR: values[ 3 ] = window->u.frame.height >> 2; values[ 1 ] = window->u.frame.height - values[ 3 ]; } if( window->u.frame.decoration & DEC_BORDER && values[ 2 ] > 0 && values[ 3 ] > 0 && !( i == BR_T && ( window->u.frame.decoration & DEC_TITLE ) ) ) { xcb_configure_window( c, window->u.frame.border_regions[ i ], XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values ); xcb_map_window( c, window->u.frame.border_regions[ i ] ); } else xcb_unmap_window( c, window->u.frame.border_regions[ i ] ); } synthetic_configure_notify( window ); } static void frame_configure_request( struct gwm_window *window, xcb_configure_request_event_t *ev ) { int x, y, child_x, child_y, frame_x, frame_y, frame_width, frame_height; uint32_t values[ 5 ]; if( ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH ) /* Ignore border width request, but remember what was asked for. */ window->u.frame.child->u.managed.border_width = ev->border_width; translate_frame_to_child( window, &child_x, &child_y, window->u.frame.x, window->u.frame.y, window->u.frame.child->u.managed.border_width, window->u.frame.child->u.managed.win_gravity ); x = ev->value_mask & XCB_CONFIG_WINDOW_X ? ev->x : child_x; y = ev->value_mask & XCB_CONFIG_WINDOW_Y ? ev->y : child_y; translate_child_to_frame( window, &frame_x, &frame_y, &frame_width, &frame_height, x, y, ev->width, ev->height, window->u.frame.child->u.managed.border_width, window->u.frame.child->u.managed.win_gravity ); values[ 0 ] = frame_x; values[ 1 ] = frame_y; values[ 2 ] = frame_width; values[ 3 ] = frame_height; values[ 4 ] = ev->stack_mode; if( frame_x != window->u.frame.x || frame_y != window->u.frame.y || frame_width != window->u.frame.width || frame_height != window->u.frame.height || ( ev->value_mask & XCB_CONFIG_WINDOW_STACK_MODE ) ) { /* We'll also notify the client of any changes, in the ConfigureNotify handler for the event we expect to receive in response to this request. */ xcb_configure_window( c, window->w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | ( ev->value_mask & XCB_CONFIG_WINDOW_STACK_MODE ), values ); if( frame_width != window->u.frame.width || frame_height != window->u.frame.height ) { values[ 0 ] = frame_width - frame_l( window, FALSE ) - frame_r( window, FALSE ); values[ 1 ] = frame_height - frame_t( window, FALSE ) - frame_b( window, FALSE ); xcb_configure_window( c, ev->window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values ); } } else /* Send a synthetic ConfigureNotify indicating the client's configuration in the root co-ordinate space (ICCCM 2.0, section 4.1.5). */ synthetic_configure_notify( window ); } const event_handler frame_handlers[ NUM_EXTENDED_EVENTS ] = { NULL, /* Error */ NULL, /* Reply */ NULL, /* KeyPress */ NULL, /* KeyRelease */ (event_handler) frame_button_press, (event_handler) frame_button_release, (event_handler) frame_motion_notify, (event_handler) frame_enter_notify, NULL, /* LeaveNotify */ NULL, /* FocusIn */ NULL, /* FocusOut */ NULL, /* KeymapNotify */ (event_handler) generic_expose, NULL, /* GraphicsExpose */ NULL, /* NoExposure */ NULL, /* VisibilityNotify */ NULL, /* CreateNotify */ (event_handler) frame_destroy_notify, NULL, /* UnmapNotify */ NULL, /* MapNotify */ (event_handler) frame_map_request, NULL, /* ReparentNotify */ (event_handler) frame_configure_notify, (event_handler) frame_configure_request, NULL, /* GravityNotify */ NULL, /* ResizeRequest */ NULL, /* CirculateNotify */ NULL, /* CirculateRequest */ NULL, /* PropertyNotify */ NULL, /* SelectionClear */ NULL, /* SelectionRequest */ NULL, /* SelectionNotify */ NULL, /* ColormapNotify */ NULL, /* ClientMessage */ NULL, /* MappingNotify */ NULL, /* (synthetic) */ NULL, /* RRNotify */ NULL /* ShapeNotify */ }; const event_handler childless_handlers[ NUM_EXTENDED_EVENTS ] = { NULL, /* Error */ NULL, /* Reply */ NULL, /* KeyPress */ NULL, /* KeyRelease */ NULL, /* ButtonPress */ NULL, /* ButtonRelease */ NULL, /* MotionNotify */ NULL, /* EnterNotify */ NULL, /* LeaveNotify */ NULL, /* FocusIn */ NULL, /* FocusOut */ NULL, /* KeymapNotify */ NULL, /* Expose */ NULL, /* GraphicsExpose */ NULL, /* NoExposure */ NULL, /* VisibilityNotify */ NULL, /* CreateNotify */ (event_handler) frame_destroy_notify, NULL, /* UnmapNotify */ NULL, /* MapNotify */ (event_handler) withdrawn_map_request, NULL, /* ReparentNotify */ NULL, /* ConfigureNotify */ (event_handler) withdrawn_configure_request, NULL, /* GravityNotify */ NULL, /* ResizeRequest */ NULL, /* CirculateNotify */ NULL, /* CirculateRequest */ NULL, /* PropertyNotify */ NULL, /* SelectionClear */ NULL, /* SelectionRequest */ NULL, /* SelectionNotify */ NULL, /* ColormapNotify */ NULL, /* ClientMessage */ NULL, /* MappingNotify */ NULL, /* (synthetic) */ NULL, /* RRNotify */ NULL /* ShapeNotify */ };