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/>.
41 #include "window-table.h"
43 static void destroy_menu( struct gwm_window
*menu
) {
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
]->
73 cp
= window
->u
.menu
.items
[ window
->u
.menu
.active_item
]->
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
];
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
,
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
;
117 if( ( child
= lookup_window( ev
->event
) ) &&
118 child
->type
== WINDOW_MENUITEM
) {
122 for( i
= 0; i
< window
->u
.menu
.num_items
; i
++ )
123 if( window
->u
.menu
.items
[ i
] == child
)
126 if( i
== window
->u
.menu
.num_items
|| i
== window
->u
.menu
.active_item
)
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
;
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
;
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
)
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
) {
169 if( have_extension
[ EXT_RANDR
] ) {
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
];
181 else if( *x
>= c
->x
+ c
->width
)
182 cx
= c
->x
+ c
->width
- 1;
188 else if( *y
>= c
->y
+ c
->height
)
189 cy
= c
->y
+ c
->height
- 1;
193 if( abs( cx
- *x
) + abs( cy
- *y
) <
194 abs( best_x
- *x
) + abs( best_y
- *y
) ) {
198 if( cx
== *x
&& cy
== *y
)
206 /* Limit the width to half that of the widest CRTC containing the
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;
219 if( best_width
< *width
)
222 /* Limit the height to that of the tallest CRTC containing the
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 )
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
)
247 /* If there exists some CRTC which contains the pointer and
248 could hold the entire rectangle if it were moved, move it
250 best_x
= INT_MAX
>> 1;
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
) {
261 else if( *x
+ *width
> c
->x
+ c
->width
)
262 cx
= c
->x
+ c
->width
- *width
;
268 else if( *y
+ *height
> c
->y
+ c
->height
)
269 cy
= c
->y
+ c
->height
- *height
;
273 if( abs( cx
- *x
) + abs( cy
- *y
) <
274 abs( best_x
- *x
) + abs( best_y
- *y
) ) {
278 if( cx
== *x
&& cy
== *y
)
284 if( best_x
< INT_MAX
>> 1 ) {
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. */
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
) {
303 if( *x
+ *width
> c
->x
+ c
->width
)
304 best_x
= c
->x
+ c
->width
- *width
;
311 if( *y
+ *height
> c
->y
+ c
->height
)
312 best_y
= c
->y
+ c
->height
- *height
;
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
;
339 if( *y
+ *height
> screens
[ screen
]->height_in_pixels
)
340 *y
= screens
[ screen
]->height_in_pixels
- *height
;
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
;
358 switch( ev
->response_type
) {
359 case XCB_BUTTON_PRESS
:
362 timestamp
= bp
->time
;
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
) :
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 */
413 fit_menu( screen
, &ev_x
, &ev_y
, &menu
->u
.menu
.width
, &height
);
414 menu
->u
.menu
.width
-= 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
;
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
);
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
] = {
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
,
460 NULL
, /* KeymapNotify */
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) */
486 NULL
/* ShapeNotify */
487 }, menuitem_handlers
[ NUM_EXTENDED_EVENTS
] = {
491 NULL
, /* KeyRelease */
492 NULL
, /* ButtonPress */
493 NULL
, /* ButtonRelease */
494 NULL
, /* MotionNotify */
495 NULL
, /* EnterNotify */
496 NULL
, /* LeaveNotify */
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) */
525 NULL
/* ShapeNotify */