From 5f969c94bf635321a41be6b086892b9974f1c2d0 Mon Sep 17 00:00:00 2001 From: Vincent Povirk Date: Mon, 6 Jun 2011 18:41:14 -0500 Subject: [PATCH 5/8] winex11.drv: Grab registered hotkeys in the X server. --- dlls/winex11.drv/keyboard.c | 265 ++++++++++++++++++++++++++++++++++++- dlls/winex11.drv/window.c | 2 + dlls/winex11.drv/winex11.drv.spec | 2 + dlls/winex11.drv/x11drv.h | 3 + dlls/winex11.drv/x11drv_main.c | 2 + 5 files changed, 267 insertions(+), 7 deletions(-) diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index 42ca462..705d07b 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -63,7 +63,7 @@ WINE_DECLARE_DEBUG_CHANNEL(key); static int min_keycode, max_keycode, keysyms_per_keycode; static WORD keyc2vkey[256], keyc2scan[256]; -static int NumLockMask, ScrollLockMask, AltGrMask; /* mask in the XKeyEvent state */ +static int NumLockMask, ScrollLockMask, AnyLockMask, AltGrMask, MetaMask, SuperMask; /* mask in the XKeyEvent state */ static char KEYBOARD_MapDeadKeysym(KeySym keysym); @@ -1136,6 +1136,15 @@ static WORD EVENT_event_to_vkey( XIC xic, XKeyEvent *e) return keyc2vkey[e->keycode]; } +struct hotkey { + struct list entry; + HWND hwnd; + UINT modifiers; + UINT vk; + int x_modifiers; + int x_keycode; +}; + /*********************************************************************** * X11DRV_send_keyboard_input @@ -1267,6 +1276,51 @@ static void update_lock_state( HWND hwnd, WORD vkey, UINT state, DWORD time ) } } +static void update_modifier_state(int modifier_mask, int actual_mask, + BYTE *keystate, WORD vkey, WORD alt_vkey, WORD scan, WORD alt_scan, DWORD event_time) +{ + int key_pressed = (actual_mask & modifier_mask) != 0; + int vkey_pressed = (keystate[vkey] & 0x80) == 0x80 || + (keystate[alt_vkey] & 0x80) == 0x80; + + if (key_pressed != vkey_pressed) + { + if (key_pressed) + { + TRACE_(key)("Pressing modifier key 0x%x\n", vkey); + X11DRV_send_keyboard_input( NULL, vkey, scan, + (scan & 0x100) ? KEYEVENTF_EXTENDEDKEY : 0, event_time ); + } + else + { + if ((keystate[vkey] & 0x80) == 0x80) + { + TRACE_(key)("Releasing modifier key 0x%x\n", vkey); + X11DRV_send_keyboard_input( NULL, vkey, scan, + KEYEVENTF_KEYUP | ((scan & 0x100) ? KEYEVENTF_EXTENDEDKEY : 0), event_time ); + } + if ((keystate[alt_vkey] & 0x80) == 0x80) + { + TRACE_(key)("Releasing modifier key 0x%x\n", alt_vkey); + X11DRV_send_keyboard_input( NULL, alt_vkey, alt_scan, + KEYEVENTF_KEYUP | ((alt_scan & 0x100) ? KEYEVENTF_EXTENDEDKEY : 0), event_time ); + } + } + } +} + +static int is_hotkey_event( XKeyEvent *event ) +{ + struct x11drv_thread_data *data = x11drv_thread_data(); + struct hotkey *hotkey; + + LIST_FOR_EACH_ENTRY( hotkey, &data->hotkeys, struct hotkey, entry ) + if ((event->state & ~AnyLockMask) == hotkey->x_modifiers && event->keycode == hotkey->x_keycode) + return 1; + + return 0; +} + /*********************************************************************** * X11DRV_KeyEvent * @@ -1366,6 +1420,23 @@ void X11DRV_KeyEvent( HWND hwnd, XEvent *xev ) update_lock_state( hwnd, vkey, event->state, event_time ); + if (hwnd == GetDesktopWindow()) + { + BYTE keystate[256]; + if (event->type == KeyPress && is_hotkey_event(event) && get_async_key_state(keystate)) + { + /* This marks the start of an implicit keyboard grab, so we may not + * be aware of modifier keypresses from before this event */ + update_modifier_state(MetaMask, event->state, keystate, VK_LMENU, VK_RMENU, 0x38, 0x138, event_time); + update_modifier_state(ControlMask, event->state, keystate, VK_LCONTROL, VK_RCONTROL, 0x1D, 0x11D, event_time); + update_modifier_state(ShiftMask, event->state, keystate, VK_LSHIFT, VK_RSHIFT, 0x2A, 0x136, event_time); + update_modifier_state(SuperMask, event->state, keystate, VK_LWIN, VK_RWIN, 0x15b, 0x15c, event_time); + } + + /* If a grabbed keystroke is not converted to WM_HOTKEY, it still needs to go to the active window */ + hwnd = NULL; + } + bScan = keyc2scan[event->keycode] & 0xFF; TRACE_(key)("bScan = 0x%02x.\n", bScan); @@ -1613,19 +1684,32 @@ void X11DRV_InitKeyboard( Display *display ) int k; for (k = 0; k < keysyms_per_keycode; k += 1) - if (keycode_to_keysym(display, *kcp, k) == XK_Num_Lock) + { + KeySym sym = XKeycodeToKeysym(display, *kcp, k); + switch (sym) { - NumLockMask = 1 << i; + case XK_Num_Lock: + NumLockMask |= 1 << i; TRACE_(key)("NumLockMask is %x\n", NumLockMask); - } - else if (keycode_to_keysym(display, *kcp, k) == XK_Scroll_Lock) - { - ScrollLockMask = 1 << i; + break; + case XK_Scroll_Lock: + ScrollLockMask |= 1 << i; TRACE_(key)("ScrollLockMask is %x\n", ScrollLockMask); + break; + case XK_Meta_L: case XK_Meta_R: + MetaMask |= 1 << i; + TRACE_(key)("MetaMask is %x\n", ScrollLockMask); + break; + case XK_Super_L: case XK_Super_R: + SuperMask |= 1 << i; + TRACE_(key)("SuperMask is %x\n", ScrollLockMask); + break; } + } } } XFreeModifiermap(mmp); + AnyLockMask = LockMask|NumLockMask|ScrollLockMask; /* Detect the keyboard layout */ X11DRV_KEYBOARD_DetectLayout( display ); @@ -1939,6 +2023,173 @@ HKL CDECL X11DRV_ActivateKeyboardLayout(HKL hkl, UINT flags) } +static int is_access_error( Display *display, XErrorEvent *event, void *arg ) +{ + return (event->error_code == BadAccess); +} + +/*********************************************************************** + * RegisterHotKey (X11DRV.@) + * + * If possible, this function grabs the given key combination, ensuring that the + * appropriate key events will always get to the Wine server, even if a non-Wine + * window is active. If any client (including this one) already grabbed the + * given key combination, set an error and return FALSE. Otherwise, return TRUE. + * + * The grab should last until this thread dies, the window is destroyed, or + * UnregisterHotKey is called. + */ +BOOL CDECL X11DRV_RegisterHotKey(HWND hwnd, UINT modifiers, UINT vk) +{ + struct x11drv_thread_data *data = x11drv_init_thread_data(); + int x_modifiers = 0, x_keycode; + int lock_mask; + struct hotkey *hotkey; + + TRACE("(%p,%x,%x)\n", hwnd, modifiers, vk); + + if (modifiers & MOD_ALT) + x_modifiers |= MetaMask; + if (modifiers & MOD_CONTROL) + x_modifiers |= ControlMask; + if (modifiers & MOD_SHIFT) + x_modifiers |= ShiftMask; + if (modifiers & MOD_WIN) + x_modifiers |= SuperMask; + + for (x_keycode=min_keycode; x_keycode<=max_keycode; x_keycode++) { + if ((keyc2vkey[x_keycode] & 0xFF) == vk) + break; + } + + if (x_keycode > max_keycode) { + WARN("keycode not found\n"); + return TRUE; + } + + LIST_FOR_EACH_ENTRY( hotkey, &data->hotkeys, struct hotkey, entry ) + { + if (x_modifiers == hotkey->x_modifiers && x_keycode == hotkey->x_keycode) + { + /* We don't want to grab the same thing twice, but X allows it. */ + TRACE("key already grabbed by this thread\n"); + SetLastError(ERROR_HOTKEY_ALREADY_REGISTERED); + return FALSE; + } + } + + hotkey = HeapAlloc(GetProcessHeap(), 0, sizeof(*hotkey)); + if (!hotkey) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + + wine_tsx11_lock(); + + /* grab the key with every combination of lock bits to ignore locks */ + lock_mask = AnyLockMask; + X11DRV_expect_error(data->display, is_access_error, NULL); + do + { + XGrabKey(data->display, x_keycode, x_modifiers|lock_mask, root_window, True, GrabModeAsync, GrabModeAsync); + lock_mask = (lock_mask-1) & AnyLockMask; + } while (lock_mask != AnyLockMask); + + XSync(data->display, False); + if (X11DRV_check_error()) + { + TRACE("key already grabbed by another X client\n"); + lock_mask = AnyLockMask; + do + { + XUngrabKey(data->display, x_keycode, x_modifiers|lock_mask, root_window); + lock_mask = (lock_mask-1) & AnyLockMask; + } while (lock_mask != AnyLockMask); + wine_tsx11_unlock(); + SetLastError(ERROR_HOTKEY_ALREADY_REGISTERED); + HeapFree(GetProcessHeap(), 0, hotkey); + return FALSE; + } + + wine_tsx11_unlock(); + + hotkey->hwnd = hwnd; + hotkey->modifiers = modifiers; + hotkey->vk = vk; + hotkey->x_modifiers = x_modifiers; + hotkey->x_keycode = x_keycode; + + list_add_tail( &data->hotkeys, &hotkey->entry ); + + return TRUE; +} + +static void unregister_hotkey(Display *display, struct hotkey *hotkey) +{ + int lock_mask; + + wine_tsx11_lock(); + lock_mask = AnyLockMask; + do + { + XUngrabKey(display, hotkey->x_keycode, hotkey->x_modifiers|lock_mask, root_window); + lock_mask = (lock_mask-1) & AnyLockMask; + } while (lock_mask != AnyLockMask); + wine_tsx11_unlock(); + + list_remove( &hotkey->entry ); + HeapFree(GetProcessHeap(), 0, hotkey); +} + +/*********************************************************************** + * UnregisterHotKey (X11DRV.@) + * + * Remove a grab registered by RegisterHotKey. Silently ignore unknown grabs. + */ +void CDECL X11DRV_UnregisterHotKey(HWND hwnd, UINT modifiers, UINT vk) +{ + struct x11drv_thread_data *data = x11drv_init_thread_data(); + struct hotkey *hotkey; + + TRACE("(%p,%x,%x)\n", hwnd, modifiers, vk); + + LIST_FOR_EACH_ENTRY( hotkey, &data->hotkeys, struct hotkey, entry ) + { + if (hwnd == hotkey->hwnd && modifiers == hotkey->modifiers && vk == hotkey->vk) + { + unregister_hotkey(data->display, hotkey); + return; + } + } +} + +void X11DRV_UnregisterWindowHotkeys(HWND hwnd) +{ + struct x11drv_thread_data *data = x11drv_thread_data(); + struct hotkey *hotkey, *hotkey2; + + if (!data) return; + + LIST_FOR_EACH_ENTRY_SAFE( hotkey, hotkey2, &data->hotkeys, struct hotkey, entry ) + { + if (hwnd == NULL || hwnd == hotkey->hwnd) + unregister_hotkey(data->display, hotkey); + } +} + +void X11DRV_FreeThreadHotkeys() +{ + struct x11drv_thread_data *data = x11drv_thread_data(); + struct hotkey *hotkey, *hotkey2; + + if (!data) return; + + LIST_FOR_EACH_ENTRY_SAFE( hotkey, hotkey2, &data->hotkeys, struct hotkey, entry ) + HeapFree(GetProcessHeap(), 0, hotkey); +} + + /*********************************************************************** * X11DRV_MappingNotify */ diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 6a87afc..1c80257 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -1837,6 +1837,8 @@ void CDECL X11DRV_DestroyWindow( HWND hwnd ) struct x11drv_thread_data *thread_data = x11drv_thread_data(); struct x11drv_win_data *data; + X11DRV_UnregisterWindowHotkeys( hwnd ); + if (!(data = X11DRV_get_win_data( hwnd ))) return; if (data->pixmap) diff --git a/dlls/winex11.drv/winex11.drv.spec b/dlls/winex11.drv/winex11.drv.spec index f5c45d1..51df2f0 100644 --- a/dlls/winex11.drv/winex11.drv.spec +++ b/dlls/winex11.drv/winex11.drv.spec @@ -70,8 +70,10 @@ @ cdecl GetKeyboardLayoutName(ptr) X11DRV_GetKeyboardLayoutName @ cdecl LoadKeyboardLayout(wstr long) X11DRV_LoadKeyboardLayout @ cdecl MapVirtualKeyEx(long long long) X11DRV_MapVirtualKeyEx +@ cdecl RegisterHotKey(ptr long long) X11DRV_RegisterHotKey @ cdecl ToUnicodeEx(long long ptr ptr long long long) X11DRV_ToUnicodeEx @ cdecl UnloadKeyboardLayout(long) X11DRV_UnloadKeyboardLayout +@ cdecl UnregisterHotKey(ptr long long) X11DRV_UnregisterHotKey @ cdecl VkKeyScanEx(long long) X11DRV_VkKeyScanEx @ cdecl DestroyCursorIcon(long) X11DRV_DestroyCursorIcon @ cdecl SetCursor(long) X11DRV_SetCursor diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index 5a5d109..5c2244b 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -557,6 +557,7 @@ struct x11drv_thread_data DWORD clip_reset; /* time when clipping was last reset */ HKL kbd_layout; /* active keyboard layout */ enum { xi_unavailable = -1, xi_unknown, xi_disabled, xi_enabled } xi2_state; /* XInput2 state */ + struct list hotkeys; /* list of registered hotkeys */ }; extern struct x11drv_thread_data *x11drv_init_thread_data(void) DECLSPEC_HIDDEN; @@ -853,6 +854,8 @@ extern void ungrab_clipping_window(void) DECLSPEC_HIDDEN; extern void reset_clipping_window(void) DECLSPEC_HIDDEN; extern BOOL clip_fullscreen_window( HWND hwnd, BOOL reset ) DECLSPEC_HIDDEN; extern void X11DRV_InitKeyboard( Display *display ) DECLSPEC_HIDDEN; +extern void X11DRV_UnregisterWindowHotkeys(HWND hwnd) DECLSPEC_HIDDEN; +extern void X11DRV_FreeThreadHotkeys(void) DECLSPEC_HIDDEN; extern DWORD CDECL X11DRV_MsgWaitForMultipleObjectsEx( DWORD count, const HANDLE *handles, DWORD timeout, DWORD mask, DWORD flags ) DECLSPEC_HIDDEN; diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c index 9b01829..278ea7a 100644 --- a/dlls/winex11.drv/x11drv_main.c +++ b/dlls/winex11.drv/x11drv_main.c @@ -627,6 +627,7 @@ static void thread_detach(void) if (data->font_set) XFreeFontSet( data->display, data->font_set ); XCloseDisplay( data->display ); wine_tsx11_unlock(); + X11DRV_FreeThreadHotkeys(); HeapFree( GetProcessHeap(), 0, data ); } } @@ -695,6 +696,7 @@ struct x11drv_thread_data *x11drv_init_thread_data(void) ERR( "could not create data\n" ); ExitProcess(1); } + list_init( &data->hotkeys ); wine_tsx11_lock(); if (!(data->display = XOpenDisplay(NULL))) { -- 1.7.1