Grab keyboard from X mainloop.

Should fix small 'jerk' when starting from keybinding and having to wait for keyboard grab.
This commit is contained in:
Dave Davenport 2015-01-29 17:37:12 +01:00
parent de9e78bb57
commit f40e072802
1 changed files with 223 additions and 194 deletions

View File

@ -469,17 +469,38 @@ static int pointer_get ( Display *display, Window root, int *x, int *y )
return 0; return 0;
} }
static int take_keyboard ( Window w ) typedef enum _MainLoopEvent
{ {
int i; ML_XEVENT,
ML_TIMEOUT
} MainLoopEvent;
static inline MainLoopEvent wait_for_xevent_or_timeout ( Display *display, int x11_fd )
{
// Check if events are pending.
if ( XPending ( display ) ) {
return ML_XEVENT;
}
// If not, wait for timeout.
struct timeval tv;
fd_set in_fds;
// Create a File Description Set containing x11_fd
FD_ZERO ( &in_fds );
FD_SET ( x11_fd, &in_fds );
for ( i = 0; i < 1000; i++ ) { // Set our timer. 200ms is a decent delay
if ( XGrabKeyboard ( display, w, True, GrabModeAsync, GrabModeAsync, CurrentTime ) == GrabSuccess ) { tv.tv_usec = 200000;
return 1; tv.tv_sec = 0;
} // Wait for X Event or a Timer
if ( select ( x11_fd + 1, &in_fds, 0, 0, &tv ) == 0 ) {
return ML_TIMEOUT;
}
return ML_XEVENT;
}
struct timespec rsl = { 0, 100000L }; static int take_keyboard ( Display *display, Window w )
nanosleep ( &rsl, NULL ); {
if ( XGrabKeyboard ( display, w, True, GrabModeAsync, GrabModeAsync, CurrentTime ) == GrabSuccess ) {
return 1;
} }
return 0; return 0;
@ -1724,188 +1745,187 @@ MenuReturn menu ( char **lines, unsigned int num_lines, char **input, char *prom
XMapRaised ( display, main_window ); XMapRaised ( display, main_window );
// if grabbing keyboard failed, fall through // if grabbing keyboard failed, fall through
if ( take_keyboard ( main_window ) ) { state.selected = 0;
state.selected = 0; // The cast to unsigned in here is valid, we checked if selected_line > 0.
// The cast to unsigned in here is valid, we checked if selected_line > 0. // So its maximum range is 0-2³¹, well within the num_lines range.
// So its maximum range is 0-2³¹, well within the num_lines range. if ( ( *( state.selected_line ) ) >= 0 && (unsigned int) ( *( state.selected_line ) ) <= state.num_lines ) {
if ( ( *( state.selected_line ) ) >= 0 && (unsigned int) ( *( state.selected_line ) ) <= state.num_lines ) { state.selected = *( state.selected_line );
state.selected = *( state.selected_line ); }
state.quit = FALSE;
menu_refilter ( &state, lines, mmc, mmc_data, sorting, config.case_sensitive );
int x11_fd = ConnectionNumber ( display );
int has_keyboard = take_keyboard ( display, main_window );
while ( !state.quit ) {
// Update if requested.
if ( state.update ) {
menu_update ( &state );
} }
state.quit = FALSE; // Wait for event.
menu_refilter ( &state, lines, mmc, mmc_data, sorting, config.case_sensitive ); XEvent ev;
// Only use lazy mode above 5000 lines.
int x11_fd = ConnectionNumber ( display ); // Or if we still need to get window.
while ( !state.quit ) { MainLoopEvent mle = ML_XEVENT;
// Update if requested. // If we are in lazy mode, or trying to grab keyboard, go into timeout.
if ( state.update ) { // Otherwise continue like we had an XEvent (and we will block on fetching this event).
if ( !has_keyboard || ( state.refilter && state.num_lines > config.lazy_filter_limit ) ) {
mle = wait_for_xevent_or_timeout ( display, x11_fd );
// Whatever happened, try to get keyboard.
has_keyboard = take_keyboard ( display, main_window );
}
// If not in lazy mode, refilter.
if ( state.num_lines <= config.lazy_filter_limit ) {
if ( state.refilter ) {
menu_refilter ( &state, lines, mmc, mmc_data, sorting, config.case_sensitive );
menu_update ( &state ); menu_update ( &state );
} }
}
else if ( mle == ML_TIMEOUT ) {
// When timeout (and in lazy filter mode)
// We refilter then loop back and wait for Xevent.
if ( state.refilter ) {
menu_refilter ( &state, lines, mmc, mmc_data, sorting, config.case_sensitive );
menu_update ( &state );
}
// Return like normal.
continue;
}
// Get next event. (might block)
XNextEvent ( display, &ev );
// Wait for event. // Handle event.
XEvent ev; if ( ev.type == Expose ) {
// Only use lazy mode above 5000 lines. while ( XCheckTypedEvent ( display, Expose, &ev ) ) {
if ( state.num_lines > config.lazy_filter_limit ) { ;
// No message waiting, update been set, do timeout trick. }
if ( state.refilter && !XPending ( display ) ) { state.update = TRUE;
// This implements a lazy re-filtering. }
struct timeval tv; // Button press event.
fd_set in_fds; else if ( ev.type == ButtonPress ) {
// Create a File Description Set containing x11_fd while ( XCheckTypedEvent ( display, ButtonPress, &ev ) ) {
FD_ZERO ( &in_fds ); ;
FD_SET ( x11_fd, &in_fds ); }
menu_mouse_navigation ( &state, &( ev.xbutton ) );
}
// Paste event.
else if ( ev.type == SelectionNotify ) {
while ( XCheckTypedEvent ( display, SelectionNotify, &ev ) ) {
;
}
menu_paste ( &state, &( ev.xselection ) );
}
// Key press event.
else if ( ev.type == KeyPress ) {
do {
if ( time ) {
*time = ev.xkey.time;
}
// Set our timer. 200ms is a decent delay KeySym key = XkbKeycodeToKeysym ( display, ev.xkey.keycode, 0, 0 );
tv.tv_usec = 200000;
tv.tv_sec = 0; // Handling of paste
// Wait for X Event or a Timer if ( ( ( ( ev.xkey.state & ControlMask ) == ControlMask ) && key == XK_v ) ) {
if ( select ( x11_fd + 1, &in_fds, 0, 0, &tv ) == 0 ) { XConvertSelection ( display, ( ev.xkey.state & ShiftMask ) ?
// Timer expired, update. XA_PRIMARY : netatoms[CLIPBOARD],
menu_refilter ( &state, lines, mmc, mmc_data, sorting, config.case_sensitive ); netatoms[UTF8_STRING], netatoms[UTF8_STRING], main_window, CurrentTime );
menu_update ( &state ); }
else if ( key == XK_Insert ) {
XConvertSelection ( display, ( ev.xkey.state & ShiftMask ) ?
XA_PRIMARY : netatoms[CLIPBOARD],
netatoms[UTF8_STRING], netatoms[UTF8_STRING], main_window, CurrentTime );
}
else if ( ( ( ev.xkey.state & ControlMask ) == ControlMask ) && key == XK_slash ) {
state.retv = MENU_PREVIOUS;
*( state.selected_line ) = 0;
state.quit = TRUE;
break;
}
// Menu navigation.
else if ( ( ( ev.xkey.state & ShiftMask ) == ShiftMask ) &&
key == XK_slash ) {
state.retv = MENU_NEXT;
*( state.selected_line ) = 0;
state.quit = TRUE;
break;
}
// Toggle case sensitivity.
else if ( key == XK_grave || key == XK_dead_grave
|| key == XK_acute ) {
config.case_sensitive = !config.case_sensitive;
*( state.selected_line ) = 0;
state.refilter = TRUE;
state.update = TRUE;
if ( config.case_sensitive ) {
textbox_show ( state.case_indicator );
}
else {
textbox_hide ( state.case_indicator );
} }
} }
} // Switcher short-cut
else { else if ( ( ( ev.xkey.state & Mod1Mask ) == Mod1Mask ) &&
if ( state.refilter ) { key >= XK_1 && key <= XK_9 ) {
menu_refilter ( &state, lines, mmc, mmc_data, sorting, config.case_sensitive ); *( state.selected_line ) = ( key - XK_1 );
menu_update ( &state ); state.retv = MENU_QUICK_SWITCH;
state.quit = TRUE;
break;
} }
} // Special delete entry command.
XNextEvent ( display, &ev ); else if ( ( ( ev.xkey.state & ShiftMask ) == ShiftMask ) &&
key == XK_Delete ) {
// Handle event. if ( state.filtered[state.selected] != NULL ) {
if ( ev.type == Expose ) { *( state.selected_line ) = state.line_map[state.selected];
while ( XCheckTypedEvent ( display, Expose, &ev ) ) { state.retv = MENU_ENTRY_DELETE;
;
}
state.update = TRUE;
}
// Button press event.
else if ( ev.type == ButtonPress ) {
while ( XCheckTypedEvent ( display, ButtonPress, &ev ) ) {
;
}
menu_mouse_navigation ( &state, &( ev.xbutton ) );
}
// Paste event.
else if ( ev.type == SelectionNotify ) {
while ( XCheckTypedEvent ( display, SelectionNotify, &ev ) ) {
;
}
menu_paste ( &state, &( ev.xselection ) );
}
// Key press event.
else if ( ev.type == KeyPress ) {
do {
if ( time ) {
*time = ev.xkey.time;
}
KeySym key = XkbKeycodeToKeysym ( display, ev.xkey.keycode, 0, 0 );
// Handling of paste
if ( ( ( ( ev.xkey.state & ControlMask ) == ControlMask ) && key == XK_v ) ) {
XConvertSelection ( display, ( ev.xkey.state & ShiftMask ) ?
XA_PRIMARY : netatoms[CLIPBOARD],
netatoms[UTF8_STRING], netatoms[UTF8_STRING], main_window, CurrentTime );
}
else if ( key == XK_Insert ) {
XConvertSelection ( display, ( ev.xkey.state & ShiftMask ) ?
XA_PRIMARY : netatoms[CLIPBOARD],
netatoms[UTF8_STRING], netatoms[UTF8_STRING], main_window, CurrentTime );
}
else if ( ( ( ev.xkey.state & ControlMask ) == ControlMask ) && key == XK_slash ) {
state.retv = MENU_PREVIOUS;
*( state.selected_line ) = 0;
state.quit = TRUE; state.quit = TRUE;
break; break;
} }
// Menu navigation. }
else if ( ( ( ev.xkey.state & ShiftMask ) == ShiftMask ) && else{
key == XK_slash ) { int rc = textbox_keypress ( state.text, &ev );
state.retv = MENU_NEXT; // Row is accepted.
*( state.selected_line ) = 0; if ( rc < 0 ) {
state.quit = TRUE; if ( shift != NULL ) {
break; ( *shift ) = ( ( ev.xkey.state & ShiftMask ) == ShiftMask );
}
// Toggle case sensitivity.
else if ( key == XK_grave || key == XK_dead_grave
|| key == XK_acute ) {
config.case_sensitive = !config.case_sensitive;
*( state.selected_line ) = 0;
state.refilter = TRUE;
state.update = TRUE;
if ( config.case_sensitive ) {
textbox_show ( state.case_indicator );
} }
else {
textbox_hide ( state.case_indicator ); // If a valid item is selected, return that..
} if ( state.selected < state.filtered_lines && state.filtered[state.selected] != NULL ) {
}
// Switcher short-cut
else if ( ( ( ev.xkey.state & Mod1Mask ) == Mod1Mask ) &&
key >= XK_1 && key <= XK_9 ) {
*( state.selected_line ) = ( key - XK_1 );
state.retv = MENU_QUICK_SWITCH;
state.quit = TRUE;
break;
}
// Special delete entry command.
else if ( ( ( ev.xkey.state & ShiftMask ) == ShiftMask ) &&
key == XK_Delete ) {
if ( state.filtered[state.selected] != NULL ) {
*( state.selected_line ) = state.line_map[state.selected]; *( state.selected_line ) = state.line_map[state.selected];
state.retv = MENU_ENTRY_DELETE; if ( strlen ( state.text->text ) > 0 && rc == -2 ) {
state.quit = TRUE;
break;
}
}
else{
int rc = textbox_keypress ( state.text, &ev );
// Row is accepted.
if ( rc < 0 ) {
if ( shift != NULL ) {
( *shift ) = ( ( ev.xkey.state & ShiftMask ) == ShiftMask );
}
// If a valid item is selected, return that..
if ( state.selected < state.filtered_lines && state.filtered[state.selected] != NULL ) {
*( state.selected_line ) = state.line_map[state.selected];
if ( strlen ( state.text->text ) > 0 && rc == -2 ) {
state.retv = MENU_CUSTOM_INPUT;
}
else {
state.retv = MENU_OK;
}
}
else if ( strlen ( state.text->text ) > 0 ) {
state.retv = MENU_CUSTOM_INPUT; state.retv = MENU_CUSTOM_INPUT;
} }
else{ else {
// Nothing entered and nothing selected. state.retv = MENU_OK;
state.retv = MENU_CANCEL;
} }
state.quit = TRUE;
} }
// Key press is handled by entry box. else if ( strlen ( state.text->text ) > 0 ) {
else if ( rc > 0 ) { state.retv = MENU_CUSTOM_INPUT;
state.refilter = TRUE;
state.update = TRUE;
} }
// Other keys.
else{ else{
// unhandled key // Nothing entered and nothing selected.
menu_keyboard_navigation ( &state, key, ev.xkey.state ); state.retv = MENU_CANCEL;
} }
}
} while ( XCheckTypedEvent ( display, KeyPress, &ev ) );
}
}
release_keyboard (); state.quit = TRUE;
}
// Key press is handled by entry box.
else if ( rc > 0 ) {
state.refilter = TRUE;
state.update = TRUE;
}
// Other keys.
else{
// unhandled key
menu_keyboard_navigation ( &state, key, ev.xkey.state );
}
}
} while ( XCheckTypedEvent ( display, KeyPress, &ev ) );
}
} }
release_keyboard ();
// Update input string. // Update input string.
g_free ( *input ); g_free ( *input );
*input = g_strdup ( state.text->text ); *input = g_strdup ( state.text->text );
@ -1974,35 +1994,44 @@ void error_dialog ( char *msg )
// Display it. // Display it.
XMapRaised ( display, main_window ); XMapRaised ( display, main_window );
if ( take_keyboard ( main_window ) ) { int x11_fd = ConnectionNumber ( display );
while ( !state.quit ) { int has_keyboard = take_keyboard ( display, main_window );
// Update if requested. while ( !state.quit ) {
if ( state.update ) { // Update if requested.
textbox_draw ( state.text ); if ( state.update ) {
state.update = FALSE; textbox_draw ( state.text );
} state.update = FALSE;
// Wait for event. }
XEvent ev; // Wait for event.
XNextEvent ( display, &ev ); XEvent ev;
MainLoopEvent mle = ML_XEVENT;
if ( !has_keyboard ) {
// Handle event. mle = wait_for_xevent_or_timeout ( display, x11_fd );
if ( ev.type == Expose ) { has_keyboard = take_keyboard ( display, main_window );
while ( XCheckTypedEvent ( display, Expose, &ev ) ) { }
; if ( mle == ML_TIMEOUT ) {
} // Loop.
state.update = TRUE; continue;
} }
// Key press event. XNextEvent ( display, &ev );
else if ( ev.type == KeyPress ) {
while ( XCheckTypedEvent ( display, KeyPress, &ev ) ) {
; // Handle event.
} if ( ev.type == Expose ) {
state.quit = TRUE; while ( XCheckTypedEvent ( display, Expose, &ev ) ) {
} ;
}
state.update = TRUE;
}
// Key press event.
else if ( ev.type == KeyPress ) {
while ( XCheckTypedEvent ( display, KeyPress, &ev ) ) {
;
}
state.quit = TRUE;
} }
release_keyboard ();
} }
release_keyboard ();
} }
SwitcherMode run_switcher_window ( char **input, G_GNUC_UNUSED void *data ) SwitcherMode run_switcher_window ( char **input, G_GNUC_UNUSED void *data )