Fix missing pointer dereference and missing assignment.
[gwm.git] / menu.c
blob99d454fb795e1aee62ebbba04becb7ce8e514994
1 /*
2 * menu.c
4 * Part of gwm, the Gratuitous Window Manager,
5 * by Gary Wong, <gtw@gnu.org>.
7 * Copyright (C) 2009 Gary Wong
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of version 3 of the GNU General Public License as
11 * published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * $Id$
24 #include <config.h>
26 #include <assert.h>
27 #include <limits.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <xcb/xcb.h>
32 #include "gwm.h"
34 #include "menu.h"
36 #include "actions.h"
37 #include "frame.h"
38 #include "keyboard.h"
39 #include "managed.h"
40 #include "root.h"
41 #include "window-table.h"
43 static void destroy_menu( struct gwm_window *menu ) {
45 int i;
47 for( i = 0; i < menu->u.menu.num_items; i++ ) {
48 if( menu->u.menu.items[ i ]->u.menuitem.label )
49 free( menu->u.menu.items[ i ]->u.menuitem.label );
50 forget_window( menu->u.menu.items[ i ] );
53 free( menu->u.menu.items );
55 xcb_destroy_window( c, menu->w );
57 forget_window( menu );
60 static void menu_button_release( struct gwm_window *window,
61 xcb_button_release_event_t *ev ) {
63 struct gwm_window *window_param;
64 void ( *action )( struct gwm_window *window, xcb_generic_event_t *ev,
65 union callback_param cp );
66 union callback_param cp;
68 if( final_release( ev ) ) {
69 if( window->u.menu.active_item >= 0 ) {
70 window_param = lookup_window( window->u.menu.window_param );
71 action = window->u.menu.items[ window->u.menu.active_item ]->
72 u.menuitem.action;
73 cp = window->u.menu.items[ window->u.menu.active_item ]->
74 u.menuitem.cp;
75 } else
76 action = NULL;
78 destroy_menu( window );
80 if( action && window_param )
81 action( window_param, (xcb_generic_event_t *) ev, cp );
85 static void deactivate_menu_item( struct gwm_window *window,
86 xcb_enter_notify_event_t *ev ) {
88 struct gwm_window *window_param,
89 *child = window->u.menu.items[ window->u.menu.active_item ];
90 int width, height;
91 uint32_t n;
93 window_size( window->u.menu.items[ 0 ], &width, &height );
95 n = gwm_screens[ window->screen ].pixels[ COL_MENU_INACTIVE_BACK ];
96 xcb_change_window_attributes( c, child->w, XCB_CW_BACK_PIXEL, &n );
97 queue_window_update( child, 0, 0, window->u.menu.width,
98 height, FALSE );
100 if( child->u.menuitem.leave_action &&
101 ( window_param = lookup_window( window->u.menu.window_param ) ) )
102 child->u.menuitem.leave_action( window_param,
103 (xcb_generic_event_t *) ev,
104 child->u.menuitem.cp );
106 window->u.menu.active_item = -1;
109 static void menu_enter_notify( struct gwm_window *window,
110 xcb_enter_notify_event_t *ev ) {
112 struct gwm_window *child;
114 if( !pointer_demux )
115 return;
117 if( ( child = lookup_window( ev->event ) ) &&
118 child->type == WINDOW_MENUITEM ) {
119 uint32_t n;
120 int i;
122 for( i = 0; i < window->u.menu.num_items; i++ )
123 if( window->u.menu.items[ i ] == child )
124 break;
126 if( i == window->u.menu.num_items || i == window->u.menu.active_item )
127 return;
129 if( window->u.menu.active_item >= 0 )
130 deactivate_menu_item( window, ev );
132 if( child->u.menuitem.label ) {
133 struct gwm_window *window_param;
134 int width, height;
136 window_size( child, &width, &height );
138 n = gwm_screens[ window->screen ].pixels[ COL_MENU_ACTIVE_BACK ];
139 xcb_change_window_attributes( c, child->w, XCB_CW_BACK_PIXEL, &n );
140 queue_window_update( child, 0, 0,
141 window->u.menu.width, height, FALSE );
143 if( child->u.menuitem.enter_action &&
144 ( window_param = lookup_window(
145 window->u.menu.window_param ) ) )
146 child->u.menuitem.enter_action( window_param, \
147 (xcb_generic_event_t *) ev,
148 child->u.menuitem.cp );
150 window->u.menu.active_item = i;
151 } else
152 window->u.menu.active_item = -1;
156 static void menu_leave_notify( struct gwm_window *window,
157 xcb_leave_notify_event_t *ev ) {
159 if( !pointer_demux || ev->detail == XCB_NOTIFY_DETAIL_INFERIOR )
160 return;
162 if( window->u.menu.active_item >= 0 )
163 deactivate_menu_item( window, ev );
166 static void fit_menu( int screen, int *x, int *y, int *width, int *height ) {
168 #if USE_RANDR
169 if( have_extension[ EXT_RANDR ] ) {
170 int i;
171 int best_x = INT_MAX >> 1, best_y = 0, best_width = 0, best_height = 0;
173 /* If the pointer falls outside all CRTCs, treat it as if it were
174 moved into the closest. */
175 for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
176 struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
177 int cx, cy;
179 if( *x < c->x )
180 cx = c->x;
181 else if( *x >= c->x + c->width )
182 cx = c->x + c->width - 1;
183 else
184 cx = *x;
186 if( *y < c->y )
187 cy = c->y;
188 else if( *y >= c->y + c->height )
189 cy = c->y + c->height - 1;
190 else
191 cy = *y;
193 if( abs( cx - *x ) + abs( cy - *y ) <
194 abs( best_x - *x ) + abs( best_y - *y ) ) {
195 best_x = cx;
196 best_y = cy;
198 if( cx == *x && cy == *y )
199 break;
203 *x = best_x;
204 *y = best_y;
206 /* Limit the width to half that of the widest CRTC containing the
207 pointer. */
208 for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
209 struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
211 if( *x >= c->x && *x < c->x + c->width &&
212 *y >= c->y && *y < c->y + c->height &&
213 c->width >> 1 > best_width )
214 best_width = c->width >> 1;
217 if( best_width < 8 )
218 best_width = 8;
219 if( best_width < *width )
220 *width = best_width;
222 /* Limit the height to that of the tallest CRTC containing the
223 pointer. */
224 for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
225 struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
227 if( *x >= c->x && *x < c->x + c->width &&
228 *y >= c->y && *y < c->y + c->height && c->height > best_height )
229 best_height = c->height;
232 if( best_height < 8 )
233 best_height = 8;
234 if( best_height < *height )
235 *height = best_height;
237 /* If there exists some CRTC which contains the pointer and
238 the entire rectangle, we've finished. */
239 for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
240 struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
242 if( *x >= c->x && *x + *width <= c->x + c->width &&
243 *y >= c->y && *y + *height <= c->y + c->height )
244 return;
247 /* If there exists some CRTC which contains the pointer and
248 could hold the entire rectangle if it were moved, move it
249 into one of them. */
250 best_x = INT_MAX >> 1;
251 best_y = 0;
252 for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
253 struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
255 if( *x >= c->x && *x < c->x + c->width && *width <= c->width &&
256 *y >= c->y && *y < c->y + c->height && *height <= c->height ) {
257 int cx, cy;
259 if( *x < c->x )
260 cx = c->x;
261 else if( *x + *width > c->x + c->width )
262 cx = c->x + c->width - *width;
263 else
264 cx = *x;
266 if( *y < c->y )
267 cy = c->y;
268 else if( *y + *height > c->y + c->height )
269 cy = c->y + c->height - *height;
270 else
271 cy = *y;
273 if( abs( cx - *x ) + abs( cy - *y ) <
274 abs( best_x - *x ) + abs( best_y - *y ) ) {
275 best_x = cx;
276 best_y = cy;
278 if( cx == *x && cy == *y )
279 break;
284 if( best_x < INT_MAX >> 1 ) {
285 *x = best_x;
286 *y = best_y;
288 return;
291 /* We can't do a good job, no matter what we try. Find the
292 CRTC which will hold as much of the rectangle as possible,
293 and move it there. Prefer to show the top and the left. */
294 best_width = 0;
295 for( i = 0; i < gwm_screens[ screen ].num_crtcs; i++ ) {
296 struct gwm_crtc *c = gwm_screens[ screen ].crtcs[ i ];
297 int cw = ( c->width > *width ? *width : c->width ) +
298 ( c->height > *height ? *height : c->height );
300 if( cw > best_width ) {
301 best_width = cw;
303 if( *x + *width > c->x + c->width )
304 best_x = c->x + c->width - *width;
305 else
306 best_x = *x;
308 if( best_x < c->x )
309 best_x = c->x;
311 if( *y + *height > c->y + c->height )
312 best_y = c->y + c->height - *height;
313 else
314 best_y = *y;
316 if( best_y < c->y )
317 best_y = c->y;
321 *x = best_x;
322 *y = best_y;
324 return;
326 #endif
328 if( *width > screens[ screen ]->width_in_pixels >> 1 )
329 *width = screens[ screen ]->width_in_pixels >> 1;
331 if( *height > screens[ screen ]->height_in_pixels )
332 *height = screens[ screen ]->height_in_pixels;
334 if( *x + *width > screens[ screen ]->width_in_pixels )
335 *x = screens[ screen ]->width_in_pixels - *width;
336 if( *x < 0 )
337 *x = 0;
339 if( *y + *height > screens[ screen ]->height_in_pixels )
340 *y = screens[ screen ]->height_in_pixels - *height;
341 if( *y < 0 )
342 *y = 0;
345 extern void popup_menu( struct gwm_window *window, xcb_generic_event_t *ev,
346 int num_items, const struct menuitem *items ) {
348 struct gwm_window *menu, *item;
349 int i, y, height, *widths, *heights, ev_x, ev_y;
350 xcb_timestamp_t timestamp;
351 uint32_t values[ 4 ];
352 xcb_button_press_event_t *bp = (xcb_button_press_event_t *) ev;
353 int screen = window->screen;
355 if( num_items < 1 )
356 return;
358 switch( ev->response_type ) {
359 case XCB_BUTTON_PRESS:
360 ev_x = bp->root_x;
361 ev_y = bp->root_y;
362 timestamp = bp->time;
363 break;
365 default:
366 assert( FALSE );
369 xcb_change_active_pointer_grab( c, cursors[ CURSOR_ARROW ], timestamp,
370 XCB_EVENT_MASK_BUTTON_RELEASE |
371 XCB_EVENT_MASK_ENTER_WINDOW |
372 XCB_EVENT_MASK_LEAVE_WINDOW );
374 menu = add_window( xcb_generate_id( c ) );
375 menu->screen = screen;
376 menu->type = WINDOW_MENU;
377 menu->u.menu.window_param = window->w;
378 menu->u.menu.num_items = num_items;
379 menu->u.menu.active_item = -1;
380 menu->u.menu.has_icons = FALSE;
381 menu->u.menu.items = xmalloc( num_items * sizeof *menu->u.menu.items );
383 widths = alloca( num_items * sizeof *widths );
384 heights = alloca( num_items * sizeof *widths );
386 for( i = 0; i < num_items; i++ ) {
387 menu->u.menu.items[ i ] = item = add_window( xcb_generate_id( c ) );
388 item->screen = screen;
389 item->type = WINDOW_MENUITEM;
390 item->u.menuitem.menu = menu;
391 item->u.menuitem.label = items[ i ].label ? strdup( items[ i ].label ) :
392 NULL;
393 item->u.menuitem.action = items[ i ].action;
394 item->u.menuitem.enter_action = items[ i ].enter_action;
395 item->u.menuitem.leave_action = items[ i ].leave_action;
396 item->u.menuitem.cp = items[ i ].cp;
397 if( ( item->u.menuitem.icon = items[ i ].icon ) )
398 menu->u.menu.has_icons = TRUE;
401 menu->u.menu.width = height = 0;
402 for( i = 0; i < num_items; i++ ) {
403 window_size( menu->u.menu.items[ i ], widths + i, heights + i );
405 if( widths[ i ] > menu->u.menu.width )
406 menu->u.menu.width = widths[ i ];
408 height += heights[ i ];
411 menu->u.menu.width += 2; /* account for border */
412 height += 2;
413 fit_menu( screen, &ev_x, &ev_y, &menu->u.menu.width, &height );
414 menu->u.menu.width -= 2;
415 height -= 2;
417 values[ 0 ] = gwm_screens[ screen ].pixels[ COL_BORDER ]; /* border pixel */
418 values[ 1 ] = TRUE; /* override redirect */
419 values[ 2 ] = TRUE; /* save under */
420 values[ 3 ] = XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW |
421 XCB_EVENT_MASK_LEAVE_WINDOW;
422 xcb_create_window( c, XCB_COPY_FROM_PARENT, menu->w,
423 screens[ screen ]->root, ev_x, ev_y, menu->u.menu.width,
424 height, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT,
425 XCB_COPY_FROM_PARENT, XCB_CW_BORDER_PIXEL |
426 XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER |
427 XCB_CW_EVENT_MASK, values );
429 values[ 0 ] = gwm_screens[ screen ].pixels[
430 COL_MENU_INACTIVE_BACK ]; /* background pixel */
431 values[ 1 ] = XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_EXPOSURE;
432 y = 0;
433 for( i = 0; i < num_items; i++ ) {
434 xcb_create_window( c, XCB_COPY_FROM_PARENT, menu->u.menu.items[ i ]->w,
435 menu->w, 0, y, menu->u.menu.width, heights[ i ], 0,
436 XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
437 XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, values );
439 y += heights[ i ];
442 xcb_map_subwindows( c, menu->w );
443 xcb_map_window( c, menu->w );
445 pointer_demux = menu->w;
448 const event_handler menu_handlers[ NUM_EXTENDED_EVENTS ] = {
449 NULL, /* Error */
450 NULL, /* Reply */
451 NULL, /* KeyPress */
452 NULL, /* KeyRelease */
453 NULL, /* ButtonPress */
454 (event_handler) menu_button_release,
455 NULL, /* MotionNotify */
456 (event_handler) menu_enter_notify,
457 (event_handler) menu_leave_notify,
458 NULL, /* FocusIn */
459 NULL, /* FocusOut */
460 NULL, /* KeymapNotify */
461 NULL, /* Expose */
462 NULL, /* GraphicsExpose */
463 NULL, /* NoExposure */
464 NULL, /* VisibilityNotify */
465 NULL, /* CreateNotify */
466 NULL, /* DestroyNotify */
467 NULL, /* UnmapNotify */
468 NULL, /* MapNotify */
469 NULL, /* MapRequest */
470 NULL, /* ReparentNotify */
471 NULL, /* ConfigureNotify */
472 NULL, /* ConfigureRequest */
473 NULL, /* GravityNotify */
474 NULL, /* ResizeRequest */
475 NULL, /* CirculateNotify */
476 NULL, /* CirculateRequest */
477 NULL, /* PropertyNotify */
478 NULL, /* SelectionClear */
479 NULL, /* SelectionRequest */
480 NULL, /* SelectionNotify */
481 NULL, /* ColormapNotify */
482 NULL, /* ClientMessage */
483 NULL, /* MappingNotify */
484 NULL, /* (synthetic) */
485 NULL, /* RRNotify */
486 NULL /* ShapeNotify */
487 }, menuitem_handlers[ NUM_EXTENDED_EVENTS ] = {
488 NULL, /* Error */
489 NULL, /* Reply */
490 NULL, /* KeyPress */
491 NULL, /* KeyRelease */
492 NULL, /* ButtonPress */
493 NULL, /* ButtonRelease */
494 NULL, /* MotionNotify */
495 NULL, /* EnterNotify */
496 NULL, /* LeaveNotify */
497 NULL, /* FocusIn */
498 NULL, /* FocusOut */
499 NULL, /* KeymapNotify */
500 (event_handler) generic_expose,
501 NULL, /* GraphicsExpose */
502 NULL, /* NoExposure */
503 NULL, /* VisibilityNotify */
504 NULL, /* CreateNotify */
505 NULL, /* DestroyNotify */
506 NULL, /* UnmapNotify */
507 NULL, /* MapNotify */
508 NULL, /* MapRequest */
509 NULL, /* ReparentNotify */
510 NULL, /* ConfigureNotify */
511 NULL, /* ConfigureRequest */
512 NULL, /* GravityNotify */
513 NULL, /* ResizeRequest */
514 NULL, /* CirculateNotify */
515 NULL, /* CirculateRequest */
516 NULL, /* PropertyNotify */
517 NULL, /* SelectionClear */
518 NULL, /* SelectionRequest */
519 NULL, /* SelectionNotify */
520 NULL, /* ColormapNotify */
521 NULL, /* ClientMessage */
522 NULL, /* MappingNotify */
523 NULL, /* (synthetic) */
524 NULL, /* RRNotify */
525 NULL /* ShapeNotify */