/* * menu.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 #include #include "gwm.h" #include "menu.h" #include "actions.h" #include "frame.h" #include "keyboard.h" #include "managed.h" #include "root.h" #include "window-table.h" static void destroy_menu( struct gwm_window *menu ) { int i; for( i = 0; i < menu->u.menu.num_items; i++ ) { if( menu->u.menu.items[ i ]->u.menuitem.label ) free( menu->u.menu.items[ i ]->u.menuitem.label ); forget_window( menu->u.menu.items[ i ] ); } free( menu->u.menu.items ); xcb_destroy_window( c, menu->w ); forget_window( menu ); } static void menu_button_release( struct gwm_window *window, xcb_button_release_event_t *ev ) { struct gwm_window *window_param; void ( *action )( struct gwm_window *window, xcb_generic_event_t *ev, union callback_param cp ); union callback_param cp; if( final_release( ev ) ) { if( window->u.menu.active_item >= 0 ) { window_param = lookup_window( window->u.menu.window_param ); action = window->u.menu.items[ window->u.menu.active_item ]-> u.menuitem.action; cp = window->u.menu.items[ window->u.menu.active_item ]-> u.menuitem.cp; } else action = NULL; destroy_menu( window ); if( action && window_param ) action( window_param, (xcb_generic_event_t *) ev, cp ); } } static void deactivate_menu_item( struct gwm_window *window, xcb_enter_notify_event_t *ev ) { struct gwm_window *window_param, *child = window->u.menu.items[ window->u.menu.active_item ]; int width, height; uint32_t n; window_size( window->u.menu.items[ 0 ], &width, &height ); n = gwm_screens[ window->screen ].pixels[ COL_MENU_INACTIVE_BACK ]; xcb_change_window_attributes( c, child->w, XCB_CW_BACK_PIXEL, &n ); queue_window_update( child, 0, 0, window->u.menu.width, height, FALSE ); if( child->u.menuitem.leave_action && ( window_param = lookup_window( window->u.menu.window_param ) ) ) child->u.menuitem.leave_action( window_param, (xcb_generic_event_t *) ev, child->u.menuitem.cp ); window->u.menu.active_item = -1; } static void menu_enter_notify( struct gwm_window *window, xcb_enter_notify_event_t *ev ) { struct gwm_window *child; if( !pointer_demux ) return; if( ( child = lookup_window( ev->event ) ) && child->type == WINDOW_MENUITEM ) { uint32_t n; int i; for( i = 0; i < window->u.menu.num_items; i++ ) if( window->u.menu.items[ i ] == child ) break; if( i == window->u.menu.num_items || i == window->u.menu.active_item ) return; if( window->u.menu.active_item >= 0 ) deactivate_menu_item( window, ev ); if( child->u.menuitem.label ) { struct gwm_window *window_param; int width, height; window_size( child, &width, &height ); n = gwm_screens[ window->screen ].pixels[ COL_MENU_ACTIVE_BACK ]; xcb_change_window_attributes( c, child->w, XCB_CW_BACK_PIXEL, &n ); queue_window_update( child, 0, 0, window->u.menu.width, height, FALSE ); if( child->u.menuitem.enter_action && ( window_param = lookup_window( window->u.menu.window_param ) ) ) child->u.menuitem.enter_action( window_param, \ (xcb_generic_event_t *) ev, child->u.menuitem.cp ); window->u.menu.active_item = i; } else window->u.menu.active_item = -1; } } static void menu_leave_notify( struct gwm_window *window, xcb_leave_notify_event_t *ev ) { if( !pointer_demux || ev->detail == XCB_NOTIFY_DETAIL_INFERIOR ) return; if( window->u.menu.active_item >= 0 ) 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 ) { struct gwm_window *menu, *item; int i, y, height, *widths, *heights, ev_x, ev_y; xcb_timestamp_t timestamp; uint32_t values[ 4 ]; xcb_button_press_event_t *bp = (xcb_button_press_event_t *) ev; int screen = window->screen; if( num_items < 1 ) return; switch( ev->response_type ) { case XCB_BUTTON_PRESS: ev_x = bp->root_x; ev_y = bp->root_y; timestamp = bp->time; break; default: assert( FALSE ); } xcb_change_active_pointer_grab( c, cursors[ CURSOR_ARROW ], timestamp, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW ); menu = add_window( xcb_generate_id( c ) ); menu->screen = screen; menu->type = WINDOW_MENU; menu->u.menu.window_param = window->w; menu->u.menu.num_items = num_items; menu->u.menu.active_item = -1; menu->u.menu.has_icons = FALSE; menu->u.menu.items = xmalloc( num_items * sizeof *menu->u.menu.items ); widths = alloca( num_items * sizeof *widths ); heights = alloca( num_items * sizeof *widths ); for( i = 0; i < num_items; i++ ) { menu->u.menu.items[ i ] = item = add_window( xcb_generate_id( c ) ); item->screen = screen; item->type = WINDOW_MENUITEM; item->u.menuitem.menu = menu; item->u.menuitem.label = items[ i ].label ? strdup( items[ i ].label ) : NULL; item->u.menuitem.action = items[ i ].action; item->u.menuitem.enter_action = items[ i ].enter_action; item->u.menuitem.leave_action = items[ i ].leave_action; item->u.menuitem.cp = items[ i ].cp; if( ( item->u.menuitem.icon = items[ i ].icon ) ) menu->u.menu.has_icons = TRUE; } menu->u.menu.width = height = 0; for( i = 0; i < num_items; i++ ) { window_size( menu->u.menu.items[ i ], widths + i, heights + i ); if( widths[ i ] > menu->u.menu.width ) menu->u.menu.width = widths[ i ]; height += heights[ i ]; } 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 */ values[ 2 ] = TRUE; /* save under */ values[ 3 ] = XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW; xcb_create_window( c, XCB_COPY_FROM_PARENT, menu->w, screens[ screen ]->root, ev_x, ev_y, menu->u.menu.width, height, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER | XCB_CW_EVENT_MASK, values ); values[ 0 ] = gwm_screens[ screen ].pixels[ COL_MENU_INACTIVE_BACK ]; /* background pixel */ values[ 1 ] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_EXPOSURE; y = 0; for( i = 0; i < num_items; i++ ) { xcb_create_window( c, XCB_COPY_FROM_PARENT, menu->u.menu.items[ i ]->w, menu->w, 0, y, menu->u.menu.width, heights[ i ], 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, values ); y += heights[ i ]; } xcb_map_subwindows( c, menu->w ); xcb_map_window( c, menu->w ); pointer_demux = menu->w; } const event_handler menu_handlers[ NUM_EXTENDED_EVENTS ] = { NULL, /* Error */ NULL, /* Reply */ NULL, /* KeyPress */ NULL, /* KeyRelease */ NULL, /* ButtonPress */ (event_handler) menu_button_release, NULL, /* MotionNotify */ (event_handler) menu_enter_notify, (event_handler) menu_leave_notify, NULL, /* FocusIn */ NULL, /* FocusOut */ NULL, /* KeymapNotify */ NULL, /* Expose */ NULL, /* GraphicsExpose */ NULL, /* NoExposure */ NULL, /* VisibilityNotify */ NULL, /* CreateNotify */ NULL, /* DestroyNotify */ NULL, /* UnmapNotify */ NULL, /* MapNotify */ NULL, /* MapRequest */ NULL, /* ReparentNotify */ NULL, /* ConfigureNotify */ NULL, /* ConfigureRequest */ NULL, /* GravityNotify */ NULL, /* ResizeRequest */ NULL, /* CirculateNotify */ NULL, /* CirculateRequest */ NULL, /* PropertyNotify */ NULL, /* SelectionClear */ NULL, /* SelectionRequest */ NULL, /* SelectionNotify */ NULL, /* ColormapNotify */ NULL, /* ClientMessage */ NULL, /* MappingNotify */ NULL, /* (synthetic) */ NULL, /* RRNotify */ NULL /* ShapeNotify */ }, menuitem_handlers[ NUM_EXTENDED_EVENTS ] = { NULL, /* Error */ NULL, /* Reply */ NULL, /* KeyPress */ NULL, /* KeyRelease */ NULL, /* ButtonPress */ NULL, /* ButtonRelease */ NULL, /* MotionNotify */ NULL, /* EnterNotify */ NULL, /* LeaveNotify */ NULL, /* FocusIn */ NULL, /* FocusOut */ NULL, /* KeymapNotify */ (event_handler) generic_expose, NULL, /* GraphicsExpose */ NULL, /* NoExposure */ NULL, /* VisibilityNotify */ NULL, /* CreateNotify */ NULL, /* DestroyNotify */ NULL, /* UnmapNotify */ NULL, /* MapNotify */ NULL, /* MapRequest */ NULL, /* ReparentNotify */ NULL, /* ConfigureNotify */ NULL, /* ConfigureRequest */ NULL, /* GravityNotify */ NULL, /* ResizeRequest */ NULL, /* CirculateNotify */ NULL, /* CirculateRequest */ NULL, /* PropertyNotify */ NULL, /* SelectionClear */ NULL, /* SelectionRequest */ NULL, /* SelectionNotify */ NULL, /* ColormapNotify */ NULL, /* ClientMessage */ NULL, /* MappingNotify */ NULL, /* (synthetic) */ NULL, /* RRNotify */ NULL /* ShapeNotify */ };