diff --git a/dlls/user32/input.c b/dlls/user32/input.c index 28377e71a4b..335be1f3f1a 100644 --- a/dlls/user32/input.c +++ b/dlls/user32/input.c @@ -983,9 +983,25 @@ UINT WINAPI GetKeyboardLayoutList(INT nBuff, HKL *layouts) */ BOOL WINAPI RegisterHotKey(HWND hwnd,INT id,UINT modifiers,UINT vk) { - static int once; - if (!once++) FIXME_(keyboard)("(%p,%d,0x%08x,%X): stub\n",hwnd,id,modifiers,vk); - return TRUE; + BOOL ret; + + TRACE_(keyboard)("(%p,%d,0x%08x,%X)\n",hwnd,id,modifiers,vk); + + /* FIXME: Register hotkey with user driver. */ + + SERVER_START_REQ( register_hotkey ) + { + req->window = wine_server_user_handle( hwnd ); + req->id = id; + req->flags = modifiers; + req->vkey = vk; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + + /* FIXME: Unregister new or replaced hotkey with user driver if necessary. */ + + return ret; } /*********************************************************************** @@ -993,9 +1009,21 @@ BOOL WINAPI RegisterHotKey(HWND hwnd,INT id,UINT modifiers,UINT vk) */ BOOL WINAPI UnregisterHotKey(HWND hwnd,INT id) { - static int once; - if (!once++) FIXME_(keyboard)("(%p,%d): stub\n",hwnd,id); - return TRUE; + BOOL ret; + + TRACE_(keyboard)("(%p,%d)\n",hwnd,id); + + SERVER_START_REQ( unregister_hotkey ) + { + req->window = wine_server_user_handle( hwnd ); + req->id = id; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + + /* FIXME: Unregister hotkey with user driver if necessary. */ + + return ret; } /*********************************************************************** diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index 1beed35b875..7585c2c9fc6 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -13155,8 +13155,8 @@ static void test_hotkey(void) SetLastError(0xdeadbeef); ret = UnregisterHotKey(NULL, 0); - todo_wine ok(ret == FALSE, "expected FALSE, got %i\n", ret); - todo_wine ok(GetLastError() == ERROR_HOTKEY_NOT_REGISTERED, "unexpected error %d\n", GetLastError()); + ok(ret == FALSE, "expected FALSE, got %i\n", ret); + ok(GetLastError() == ERROR_HOTKEY_NOT_REGISTERED, "unexpected error %d\n", GetLastError()); if (ret == TRUE) { @@ -13247,7 +13247,7 @@ static void test_hotkey(void) } DispatchMessage(&msg); } - ok_sequence(WmHotkeyPress, "window hotkey press", FALSE); + ok_sequence(WmHotkeyPress, "window hotkey press", TRUE); key_state = GetAsyncKeyState(hotkey_letter); ok((key_state & 0x8000) == 0x8000, "unexpected key state %x\n", key_state); @@ -13255,7 +13255,7 @@ static void test_hotkey(void) keybd_event(hotkey_letter, 0, KEYEVENTF_KEYUP, 0); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg); - ok_sequence(WmHotkeyRelease, "window hotkey release", FALSE); + ok_sequence(WmHotkeyRelease, "window hotkey release", TRUE); keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, 0); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) @@ -13278,7 +13278,7 @@ static void test_hotkey(void) } DispatchMessage(&msg); } - ok_sequence(WmHotkeyCombined, "window hotkey combined", FALSE); + ok_sequence(WmHotkeyCombined, "window hotkey combined", TRUE); /* Register same hwnd/id with different key combination */ ret = RegisterHotKey(test_window, 5, 0, hotkey_letter); @@ -13307,7 +13307,7 @@ static void test_hotkey(void) } DispatchMessage(&msg); } - ok_sequence(WmHotkeyNew, "window hotkey new", FALSE); + ok_sequence(WmHotkeyNew, "window hotkey new", TRUE); /* Unregister hotkey properly */ ret = UnregisterHotKey(test_window, 5); @@ -13351,7 +13351,7 @@ static void test_hotkey(void) ok(msg.hwnd != NULL, "unexpected thread message %x\n", msg.message); DispatchMessage(&msg); } - ok_sequence(WmHotkeyPress, "thread hotkey press", FALSE); + ok_sequence(WmHotkeyPress, "thread hotkey press", TRUE); keybd_event(hotkey_letter, 0, KEYEVENTF_KEYUP, 0); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) @@ -13359,7 +13359,7 @@ static void test_hotkey(void) ok(msg.hwnd != NULL, "unexpected thread message %x\n", msg.message); DispatchMessage(&msg); } - ok_sequence(WmHotkeyRelease, "thread hotkey release", FALSE); + ok_sequence(WmHotkeyRelease, "thread hotkey release", TRUE); keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, 0); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 6e96f977f01..5cb24677c41 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -3724,6 +3724,42 @@ struct set_user_object_info_reply +struct register_hotkey_request +{ + struct request_header __header; + user_handle_t window; + int id; + unsigned int flags; + unsigned int vkey; + char __pad_28[4]; +}; +struct register_hotkey_reply +{ + struct reply_header __header; + int replaced; + unsigned int flags; + unsigned int vkey; + char __pad_20[4]; +}; + + + +struct unregister_hotkey_request +{ + struct request_header __header; + user_handle_t window; + int id; + char __pad_20[4]; +}; +struct unregister_hotkey_reply +{ + struct reply_header __header; + unsigned int flags; + unsigned int vkey; +}; + + + struct attach_thread_input_request { struct request_header __header; @@ -5023,6 +5059,8 @@ enum request REQ_set_thread_desktop, REQ_enum_desktop, REQ_set_user_object_info, + REQ_register_hotkey, + REQ_unregister_hotkey, REQ_attach_thread_input, REQ_get_thread_input, REQ_get_last_input_time, @@ -5275,6 +5313,8 @@ union generic_request struct set_thread_desktop_request set_thread_desktop_request; struct enum_desktop_request enum_desktop_request; struct set_user_object_info_request set_user_object_info_request; + struct register_hotkey_request register_hotkey_request; + struct unregister_hotkey_request unregister_hotkey_request; struct attach_thread_input_request attach_thread_input_request; struct get_thread_input_request get_thread_input_request; struct get_last_input_time_request get_last_input_time_request; @@ -5525,6 +5565,8 @@ union generic_reply struct set_thread_desktop_reply set_thread_desktop_reply; struct enum_desktop_reply enum_desktop_reply; struct set_user_object_info_reply set_user_object_info_reply; + struct register_hotkey_reply register_hotkey_reply; + struct unregister_hotkey_reply unregister_hotkey_reply; struct attach_thread_input_reply attach_thread_input_reply; struct get_thread_input_reply get_thread_input_reply; struct get_last_input_time_reply get_last_input_time_reply; @@ -5592,6 +5634,6 @@ union generic_reply struct set_suspend_context_reply set_suspend_context_reply; }; -#define SERVER_PROTOCOL_VERSION 423 +#define SERVER_PROTOCOL_VERSION 424 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/protocol.def b/server/protocol.def index f5ebbe7c2ca..767be04d93c 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2632,6 +2632,29 @@ enum coords_relative #define SET_USER_OBJECT_FLAGS 1 +/* Register a hotkey */ +@REQ(register_hotkey) + user_handle_t window; /* handle to the window */ + int id; /* hotkey identifier */ + unsigned int flags; /* modifier keys */ + unsigned int vkey; /* virtual key code */ +@REPLY + int replaced; /* did we replace an existing hotkey? */ + unsigned int flags; /* flags of replaced hotkey */ + unsigned int vkey; /* virtual key code of replaced hotkey */ +@END + + +/* Unregister a hotkey */ +@REQ(unregister_hotkey) + user_handle_t window; /* handle to the window */ + int id; /* hotkey identifier */ +@REPLY + unsigned int flags; /* flags of removed hotkey */ + unsigned int vkey; /* virtual key code of removed hotkey */ +@END + + /* Attach (or detach) thread inputs */ @REQ(attach_thread_input) thread_id_t tid_from; /* thread to be attached */ diff --git a/server/queue.c b/server/queue.c index d514a529a68..2fceacd3bba 100644 --- a/server/queue.c +++ b/server/queue.c @@ -136,6 +136,16 @@ struct msg_queue timeout_t last_get_msg; /* time of last get message call */ }; +struct hotkey +{ + struct list entry; /* entry in desktop hotkey list */ + struct msg_queue *queue; /* queue owning this hotkey */ + user_handle_t win; /* window handle */ + int id; /* hotkey id */ + unsigned int vkey; /* virtual key code */ + unsigned int flags; /* key modifiers */ +}; + static void msg_queue_dump( struct object *obj, int verbose ); static int msg_queue_add_queue( struct object *obj, struct wait_queue_entry *entry ); static void msg_queue_remove_queue( struct object *obj, struct wait_queue_entry *entry ); @@ -905,11 +915,21 @@ static void msg_queue_destroy( struct object *obj ) { struct msg_queue *queue = (struct msg_queue *)obj; struct list *ptr; + struct hotkey *hotkey, *hotkey2; int i; cleanup_results( queue ); for (i = 0; i < NB_MSG_KINDS; i++) empty_msg_list( &queue->msg_list[i] ); + LIST_FOR_EACH_ENTRY_SAFE( hotkey, hotkey2, &queue->input->desktop->hotkeys, struct hotkey, entry ) + { + if (hotkey->queue == queue) + { + list_remove( &hotkey->entry ); + free( hotkey ); + } + } + while ((ptr = list_head( &queue->pending_timers ))) { struct timer *timer = LIST_ENTRY( ptr, struct timer, entry ); @@ -1921,6 +1941,21 @@ void post_win_event( struct thread *thread, unsigned int event, } } +/* free all hotkeys on a desktop, optionally filtering by window */ +void free_hotkeys( struct desktop *desktop, user_handle_t window ) +{ + struct hotkey *hotkey, *hotkey2; + + LIST_FOR_EACH_ENTRY_SAFE( hotkey, hotkey2, &desktop->hotkeys, struct hotkey, entry ) + { + if (!window || hotkey->win == window) + { + list_remove( &hotkey->entry ); + free( hotkey ); + } + } +} + /* check if the thread owning the window is hung */ DECL_HANDLER(is_window_hung) @@ -2364,6 +2399,123 @@ DECL_HANDLER(kill_win_timer) release_object( thread ); } +DECL_HANDLER(register_hotkey) +{ + struct desktop *desktop; + user_handle_t win_handle = req->window; + struct hotkey *hotkey; + struct hotkey *new_hotkey = NULL; + struct thread *thread; + const int modifier_flags = MOD_ALT|MOD_CONTROL|MOD_SHIFT|MOD_WIN; + + if (!(desktop = get_thread_desktop( current, 0 ))) return; + + if (win_handle) + { + if (!get_user_object_handle( &win_handle, USER_WINDOW )) + { + release_object( desktop ); + set_win32_error( ERROR_INVALID_WINDOW_HANDLE ); + return; + } + + thread = get_window_thread( win_handle ); + if (thread) release_object( thread ); + + if (thread != current) + { + release_object( desktop ); + set_win32_error( ERROR_WINDOW_OF_OTHER_THREAD ); + return; + } + } + + LIST_FOR_EACH_ENTRY( hotkey, &desktop->hotkeys, struct hotkey, entry ) + { + if (req->vkey == hotkey->vkey && + (req->flags & modifier_flags) == (hotkey->flags & modifier_flags)) + { + release_object( desktop ); + set_win32_error( ERROR_HOTKEY_ALREADY_REGISTERED ); + return; + } + if (current->queue == hotkey->queue && win_handle == hotkey->win && req->id == hotkey->id) + new_hotkey = hotkey; + } + + if (new_hotkey) + { + reply->replaced = 1; + reply->flags = new_hotkey->flags; + reply->vkey = new_hotkey->vkey; + } + else + { + new_hotkey = mem_alloc( sizeof(*new_hotkey) ); + if (new_hotkey) + { + list_add_tail( &desktop->hotkeys, &new_hotkey->entry ); + new_hotkey->queue = current->queue; + new_hotkey->win = win_handle; + new_hotkey->id = req->id; + } + } + + if (new_hotkey) + { + new_hotkey->flags = req->flags; + new_hotkey->vkey = req->vkey; + } + + release_object( desktop ); +} + +DECL_HANDLER(unregister_hotkey) +{ + struct desktop *desktop; + user_handle_t win_handle = req->window; + struct hotkey *hotkey; + struct thread *thread; + + if (!(desktop = get_thread_desktop( current, 0 ))) return; + + if (win_handle) + { + if (!get_user_object_handle( &win_handle, USER_WINDOW )) + { + release_object( desktop ); + set_win32_error( ERROR_INVALID_WINDOW_HANDLE ); + return; + } + + thread = get_window_thread( win_handle ); + if (thread) release_object( thread ); + + if (thread != current) + { + release_object( desktop ); + set_win32_error( ERROR_WINDOW_OF_OTHER_THREAD ); + return; + } + } + + LIST_FOR_EACH_ENTRY( hotkey, &desktop->hotkeys, struct hotkey, entry ) + { + if (current->queue == hotkey->queue && win_handle == hotkey->win && req->id == hotkey->id) + goto found; + } + + release_object( desktop ); + set_win32_error( ERROR_HOTKEY_NOT_REGISTERED ); + return; + +found: + reply->flags = hotkey->flags; + reply->vkey = hotkey->vkey; + list_remove( &hotkey->entry ); + free( hotkey ); + release_object( desktop ); +} /* attach (or detach) thread inputs */ DECL_HANDLER(attach_thread_input) diff --git a/server/request.h b/server/request.h index cab8d3030ad..d2ca2f662b8 100644 --- a/server/request.h +++ b/server/request.h @@ -291,6 +291,8 @@ DECL_HANDLER(get_thread_desktop); DECL_HANDLER(set_thread_desktop); DECL_HANDLER(enum_desktop); DECL_HANDLER(set_user_object_info); +DECL_HANDLER(register_hotkey); +DECL_HANDLER(unregister_hotkey); DECL_HANDLER(attach_thread_input); DECL_HANDLER(get_thread_input); DECL_HANDLER(get_last_input_time); @@ -542,6 +544,8 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] = (req_handler)req_set_thread_desktop, (req_handler)req_enum_desktop, (req_handler)req_set_user_object_info, + (req_handler)req_register_hotkey, + (req_handler)req_unregister_hotkey, (req_handler)req_attach_thread_input, (req_handler)req_get_thread_input, (req_handler)req_get_last_input_time, @@ -1705,6 +1709,21 @@ C_ASSERT( sizeof(struct set_user_object_info_request) == 24 ); C_ASSERT( FIELD_OFFSET(struct set_user_object_info_reply, is_desktop) == 8 ); C_ASSERT( FIELD_OFFSET(struct set_user_object_info_reply, old_obj_flags) == 12 ); C_ASSERT( sizeof(struct set_user_object_info_reply) == 16 ); +C_ASSERT( FIELD_OFFSET(struct register_hotkey_request, window) == 12 ); +C_ASSERT( FIELD_OFFSET(struct register_hotkey_request, id) == 16 ); +C_ASSERT( FIELD_OFFSET(struct register_hotkey_request, flags) == 20 ); +C_ASSERT( FIELD_OFFSET(struct register_hotkey_request, vkey) == 24 ); +C_ASSERT( sizeof(struct register_hotkey_request) == 32 ); +C_ASSERT( FIELD_OFFSET(struct register_hotkey_reply, replaced) == 8 ); +C_ASSERT( FIELD_OFFSET(struct register_hotkey_reply, flags) == 12 ); +C_ASSERT( FIELD_OFFSET(struct register_hotkey_reply, vkey) == 16 ); +C_ASSERT( sizeof(struct register_hotkey_reply) == 24 ); +C_ASSERT( FIELD_OFFSET(struct unregister_hotkey_request, window) == 12 ); +C_ASSERT( FIELD_OFFSET(struct unregister_hotkey_request, id) == 16 ); +C_ASSERT( sizeof(struct unregister_hotkey_request) == 24 ); +C_ASSERT( FIELD_OFFSET(struct unregister_hotkey_reply, flags) == 8 ); +C_ASSERT( FIELD_OFFSET(struct unregister_hotkey_reply, vkey) == 12 ); +C_ASSERT( sizeof(struct unregister_hotkey_reply) == 16 ); C_ASSERT( FIELD_OFFSET(struct attach_thread_input_request, tid_from) == 12 ); C_ASSERT( FIELD_OFFSET(struct attach_thread_input_request, tid_to) == 16 ); C_ASSERT( FIELD_OFFSET(struct attach_thread_input_request, attach) == 20 ); diff --git a/server/trace.c b/server/trace.c index e81df6af941..37ea21639a1 100644 --- a/server/trace.c +++ b/server/trace.c @@ -3078,6 +3078,33 @@ static void dump_set_user_object_info_reply( const struct set_user_object_info_r dump_varargs_unicode_str( ", name=", cur_size ); } +static void dump_register_hotkey_request( const struct register_hotkey_request *req ) +{ + fprintf( stderr, " window=%08x", req->window ); + fprintf( stderr, ", id=%d", req->id ); + fprintf( stderr, ", flags=%08x", req->flags ); + fprintf( stderr, ", vkey=%08x", req->vkey ); +} + +static void dump_register_hotkey_reply( const struct register_hotkey_reply *req ) +{ + fprintf( stderr, " replaced=%d", req->replaced ); + fprintf( stderr, ", flags=%08x", req->flags ); + fprintf( stderr, ", vkey=%08x", req->vkey ); +} + +static void dump_unregister_hotkey_request( const struct unregister_hotkey_request *req ) +{ + fprintf( stderr, " window=%08x", req->window ); + fprintf( stderr, ", id=%d", req->id ); +} + +static void dump_unregister_hotkey_reply( const struct unregister_hotkey_reply *req ) +{ + fprintf( stderr, " flags=%08x", req->flags ); + fprintf( stderr, ", vkey=%08x", req->vkey ); +} + static void dump_attach_thread_input_request( const struct attach_thread_input_request *req ) { fprintf( stderr, " tid_from=%04x", req->tid_from ); @@ -4057,6 +4084,8 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_set_thread_desktop_request, (dump_func)dump_enum_desktop_request, (dump_func)dump_set_user_object_info_request, + (dump_func)dump_register_hotkey_request, + (dump_func)dump_unregister_hotkey_request, (dump_func)dump_attach_thread_input_request, (dump_func)dump_get_thread_input_request, (dump_func)dump_get_last_input_time_request, @@ -4305,6 +4334,8 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { NULL, (dump_func)dump_enum_desktop_reply, (dump_func)dump_set_user_object_info_reply, + (dump_func)dump_register_hotkey_reply, + (dump_func)dump_unregister_hotkey_reply, NULL, (dump_func)dump_get_thread_input_reply, (dump_func)dump_get_last_input_time_reply, @@ -4553,6 +4584,8 @@ static const char * const req_names[REQ_NB_REQUESTS] = { "set_thread_desktop", "enum_desktop", "set_user_object_info", + "register_hotkey", + "unregister_hotkey", "attach_thread_input", "get_thread_input", "get_last_input_time", @@ -4655,9 +4688,12 @@ static const struct { "ERROR_CLASS_DOES_NOT_EXIST", 0xc0010000 | ERROR_CLASS_DOES_NOT_EXIST }, { "ERROR_CLASS_HAS_WINDOWS", 0xc0010000 | ERROR_CLASS_HAS_WINDOWS }, { "ERROR_CLIPBOARD_NOT_OPEN", 0xc0010000 | ERROR_CLIPBOARD_NOT_OPEN }, + { "ERROR_HOTKEY_ALREADY_REGISTERED", 0xc0010000 | ERROR_HOTKEY_ALREADY_REGISTERED }, + { "ERROR_HOTKEY_NOT_REGISTERED", 0xc0010000 | ERROR_HOTKEY_NOT_REGISTERED }, { "ERROR_INVALID_CURSOR_HANDLE", 0xc0010000 | ERROR_INVALID_CURSOR_HANDLE }, { "ERROR_INVALID_INDEX", 0xc0010000 | ERROR_INVALID_INDEX }, { "ERROR_INVALID_WINDOW_HANDLE", 0xc0010000 | ERROR_INVALID_WINDOW_HANDLE }, + { "ERROR_WINDOW_OF_OTHER_THREAD", 0xc0010000 | ERROR_WINDOW_OF_OTHER_THREAD }, { "FILE_DELETED", STATUS_FILE_DELETED }, { "FILE_IS_A_DIRECTORY", STATUS_FILE_IS_A_DIRECTORY }, { "FILE_LOCK_CONFLICT", STATUS_FILE_LOCK_CONFLICT }, diff --git a/server/user.h b/server/user.h index 9df245371ac..2947de7e58b 100644 --- a/server/user.h +++ b/server/user.h @@ -70,6 +70,7 @@ struct desktop struct window *top_window; /* desktop window for this desktop */ struct window *msg_window; /* HWND_MESSAGE top window */ struct hook_table *global_hooks; /* table of global hooks on this desktop */ + struct list hotkeys; /* list of registered hotkeys */ struct timeout_user *close_timeout; /* timeout before closing the desktop */ struct thread_input *foreground_input; /* thread input of foreground thread */ unsigned int users; /* processes and threads using this desktop */ @@ -114,6 +115,7 @@ extern void post_win_event( struct thread *thread, unsigned int event, unsigned int child_id, client_ptr_t proc, const WCHAR *module, data_size_t module_size, user_handle_t handle ); +extern void free_hotkeys( struct desktop *desktop, user_handle_t window ); /* region functions */ diff --git a/server/window.c b/server/window.c index 3465eba296f..329fdb64dad 100644 --- a/server/window.c +++ b/server/window.c @@ -1727,6 +1727,7 @@ void destroy_window( struct window *win ) if (win == shell_listview) shell_listview = NULL; if (win == progman_window) progman_window = NULL; if (win == taskman_window) taskman_window = NULL; + free_hotkeys( win->desktop, win->handle ); free_user_handle( win->handle ); destroy_properties( win ); list_remove( &win->entry ); diff --git a/server/winstation.c b/server/winstation.c index 20656aa454a..e0b76138d1d 100644 --- a/server/winstation.c +++ b/server/winstation.c @@ -235,6 +235,7 @@ static struct desktop *create_desktop( const struct unicode_str *name, unsigned memset( &desktop->cursor, 0, sizeof(desktop->cursor) ); memset( desktop->keystate, 0, sizeof(desktop->keystate) ); list_add_tail( &winstation->desktops, &desktop->entry ); + list_init( &desktop->hotkeys ); } } free( full_name ); @@ -273,6 +274,7 @@ static void desktop_destroy( struct object *obj ) { struct desktop *desktop = (struct desktop *)obj; + free_hotkeys( desktop, 0 ); if (desktop->top_window) destroy_window( desktop->top_window ); if (desktop->msg_window) destroy_window( desktop->msg_window ); if (desktop->global_hooks) release_object( desktop->global_hooks );