From e3325fe58d188f511f2f10c8efba5ec4a5f73c1d Mon Sep 17 00:00:00 2001 From: Vincent Povirk Date: Mon, 6 Jun 2011 18:41:14 -0500 Subject: [PATCH 8/9] winex11.drv: Grab registered hotkeys in the X display. --- dlls/user32/driver.c | 25 ++++ dlls/user32/input.c | 31 ++++- dlls/user32/user_private.h | 2 + dlls/winex11.drv/keyboard.c | 251 +++++++++++++++++++++++++++++++++++- 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 + 8 files changed, 306 insertions(+), 12 deletions(-) diff --git a/dlls/user32/driver.c b/dlls/user32/driver.c index d152f86..6e21ca2 100644 --- a/dlls/user32/driver.c +++ b/dlls/user32/driver.c @@ -80,8 +80,10 @@ static const USER_DRIVER *load_driver(void) GET_USER_FUNC(GetKeyboardLayoutName); GET_USER_FUNC(LoadKeyboardLayout); GET_USER_FUNC(MapVirtualKeyEx); + GET_USER_FUNC(RegisterHotKey); GET_USER_FUNC(ToUnicodeEx); GET_USER_FUNC(UnloadKeyboardLayout); + GET_USER_FUNC(UnregisterHotKey); GET_USER_FUNC(VkKeyScanEx); GET_USER_FUNC(CreateCursorIcon); GET_USER_FUNC(DestroyCursorIcon); @@ -193,6 +195,11 @@ static UINT CDECL nulldrv_MapVirtualKeyEx( UINT code, UINT type, HKL layout ) return 0; } +static BOOL CDECL nulldrv_RegisterHotKey( HWND hwnd, UINT modifiers, UINT vk ) +{ + return TRUE; +} + static INT CDECL nulldrv_ToUnicodeEx( UINT virt, UINT scan, const BYTE *state, LPWSTR str, int size, UINT flags, HKL layout ) { @@ -204,6 +211,10 @@ static BOOL CDECL nulldrv_UnloadKeyboardLayout( HKL layout ) return 0; } +static void CDECL nulldrv_UnregisterHotKey( HWND hwnd, UINT modifiers, UINT vk ) +{ +} + static SHORT CDECL nulldrv_VkKeyScanEx( WCHAR ch, HKL layout ) { return -1; @@ -430,8 +441,10 @@ static USER_DRIVER null_driver = nulldrv_GetKeyboardLayoutName, nulldrv_LoadKeyboardLayout, nulldrv_MapVirtualKeyEx, + nulldrv_RegisterHotKey, nulldrv_ToUnicodeEx, nulldrv_UnloadKeyboardLayout, + nulldrv_UnregisterHotKey, nulldrv_VkKeyScanEx, /* cursor/icon functions */ nulldrv_CreateCursorIcon, @@ -528,6 +541,11 @@ static UINT CDECL loaderdrv_MapVirtualKeyEx( UINT code, UINT type, HKL layout ) return load_driver()->pMapVirtualKeyEx( code, type, layout ); } +static BOOL CDECL loaderdrv_RegisterHotKey( HWND hwnd, UINT modifiers, UINT vk ) +{ + return load_driver()->pRegisterHotKey( hwnd, modifiers, vk ); +} + static INT CDECL loaderdrv_ToUnicodeEx( UINT virt, UINT scan, const BYTE *state, LPWSTR str, int size, UINT flags, HKL layout ) { @@ -539,6 +557,11 @@ static BOOL CDECL loaderdrv_UnloadKeyboardLayout( HKL layout ) return load_driver()->pUnloadKeyboardLayout( layout ); } +static void CDECL loaderdrv_UnregisterHotKey( HWND hwnd, UINT modifiers, UINT vk ) +{ + load_driver()->pUnregisterHotKey( hwnd, modifiers, vk ); +} + static SHORT CDECL loaderdrv_VkKeyScanEx( WCHAR ch, HKL layout ) { return load_driver()->pVkKeyScanEx( ch, layout ); @@ -765,8 +788,10 @@ static USER_DRIVER lazy_load_driver = loaderdrv_GetKeyboardLayoutName, loaderdrv_LoadKeyboardLayout, loaderdrv_MapVirtualKeyEx, + loaderdrv_RegisterHotKey, loaderdrv_ToUnicodeEx, loaderdrv_UnloadKeyboardLayout, + loaderdrv_UnregisterHotKey, loaderdrv_VkKeyScanEx, /* cursor/icon functions */ loaderdrv_CreateCursorIcon, diff --git a/dlls/user32/input.c b/dlls/user32/input.c index 335be1f..25348e1 100644 --- a/dlls/user32/input.c +++ b/dlls/user32/input.c @@ -984,10 +984,13 @@ UINT WINAPI GetKeyboardLayoutList(INT nBuff, HKL *layouts) BOOL WINAPI RegisterHotKey(HWND hwnd,INT id,UINT modifiers,UINT vk) { BOOL ret; + int replaced=0; + DWORD err; TRACE_(keyboard)("(%p,%d,0x%08x,%X)\n",hwnd,id,modifiers,vk); - /* FIXME: Register hotkey with user driver. */ + if (!USER_Driver->pRegisterHotKey(hwnd, modifiers, vk)) + return FALSE; SERVER_START_REQ( register_hotkey ) { @@ -995,11 +998,23 @@ BOOL WINAPI RegisterHotKey(HWND hwnd,INT id,UINT modifiers,UINT vk) req->id = id; req->flags = modifiers; req->vkey = vk; - ret = !wine_server_call_err( req ); + if ((ret = !wine_server_call_err( req ))) + { + replaced = reply->replaced; + modifiers = reply->flags; + vk = reply->vkey; + } } SERVER_END_REQ; - /* FIXME: Unregister new or replaced hotkey with user driver if necessary. */ + if (!ret) + err = GetLastError(); + + if (!ret || replaced) + USER_Driver->pUnregisterHotKey(hwnd, modifiers, vk); + + if (!ret) + SetLastError(err); return ret; } @@ -1010,6 +1025,7 @@ BOOL WINAPI RegisterHotKey(HWND hwnd,INT id,UINT modifiers,UINT vk) BOOL WINAPI UnregisterHotKey(HWND hwnd,INT id) { BOOL ret; + UINT modifiers, vk; TRACE_(keyboard)("(%p,%d)\n",hwnd,id); @@ -1017,11 +1033,16 @@ BOOL WINAPI UnregisterHotKey(HWND hwnd,INT id) { req->window = wine_server_user_handle( hwnd ); req->id = id; - ret = !wine_server_call_err( req ); + if ((ret = !wine_server_call_err( req ))) + { + modifiers = reply->flags; + vk = reply->vkey; + } } SERVER_END_REQ; - /* FIXME: Unregister hotkey with user driver if necessary. */ + if (ret) + USER_Driver->pUnregisterHotKey(hwnd, modifiers, vk); return ret; } diff --git a/dlls/user32/user_private.h b/dlls/user32/user_private.h index 92b52a3..399974e 100644 --- a/dlls/user32/user_private.h +++ b/dlls/user32/user_private.h @@ -62,8 +62,10 @@ typedef struct tagUSER_DRIVER { BOOL (CDECL *pGetKeyboardLayoutName)(LPWSTR); HKL (CDECL *pLoadKeyboardLayout)(LPCWSTR, UINT); UINT (CDECL *pMapVirtualKeyEx)(UINT, UINT, HKL); + BOOL (CDECL *pRegisterHotKey)(HWND, UINT, UINT); INT (CDECL *pToUnicodeEx)(UINT, UINT, const BYTE *, LPWSTR, int, UINT, HKL); BOOL (CDECL *pUnloadKeyboardLayout)(HKL); + void (CDECL *pUnregisterHotKey)(HWND, UINT, UINT); SHORT (CDECL *pVkKeyScanEx)(WCHAR, HKL); /* cursor/icon functions */ void (CDECL *pCreateCursorIcon)(HCURSOR); diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index 42ca462..a66db81 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,37 @@ 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, DWORD event_time) +{ + int key_pressed = (actual_mask & modifier_mask) != 0; + int vkey_pressed = (keystate[vkey] & 0x80) == 0x80 || + (keystate[alt_vkey] & 0x80) == 0x80; + DWORD flags; + + if (key_pressed != vkey_pressed) + { + if (key_pressed) + flags = 0; + else + flags = KEYEVENTF_KEYUP; + TRACE_(key)("Adjusting modifier state vkey=0x%x, state=%i\n", vkey, key_pressed); + X11DRV_send_keyboard_input( NULL, vkey, scan, flags, 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 +1406,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, event_time); + update_modifier_state(ControlMask, event->state, keystate, VK_LCONTROL, VK_RCONTROL, 0x1D, event_time); + update_modifier_state(ShiftMask, event->state, keystate, VK_LSHIFT, VK_RSHIFT, 0x2A, event_time); + update_modifier_state(SuperMask, event->state, keystate, VK_LWIN, VK_RWIN, 0x15b, 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 +1670,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 +2009,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