w32_common: use hooks to detect parent window resize

Because VOCTRL_CHECK_EVENTS is processed asynchronously (as of 088a007,)
the GUI thread no longer gets regular wakeups, so the old check that
made sure the video window matched the parent window's size in --wid
embedding mode did not run very often. This made --wid embedding not
very usable.

Instead of polling for window size changes, use Windows hooks to react
to them when they happen. When the parent window is owned by the same
process as the video window, use a WH_CALLWNDPROC hook. When the parent
window is not owned by the same process, WinEvents must be used, which
are not as smooth, but still work for this purpose.

Since neither SetWindowsHookEx nor SetWinEventHook take a context
parameter to send data to the hook function, the hook functions must
find the child window by its class instead, so there are a few changes
to ensure this is fast and the class is unique.

This also fixes up the logic to handle window destruction. When a parent
window is destroyed, its children are also destroyed, so this gives us a
way to react to parent window destruction without polling.
This commit is contained in:
James Ross-Gowan 2016-08-25 23:07:42 +10:00
parent ef0b2e33b1
commit 31f28da0f3
1 changed files with 117 additions and 35 deletions

View File

@ -48,8 +48,6 @@
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
static const wchar_t classname[] = L"mpv";
static __thread struct vo_w32_state *w32_thread_context;
struct vo_w32_state {
@ -64,6 +62,8 @@ struct vo_w32_state {
HWND window;
HWND parent; // 0 normally, set in embedding mode
HHOOK parent_win_hook;
HWINEVENTHOOK parent_evt_hook;
HMONITOR monitor; // Handle of the current screen
struct mp_rect screenrc; // Size and virtual position of the current screen
@ -747,11 +747,19 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
// is that will make us lose WM_USER wakeups.
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
return 0;
case WM_NCDESTROY: // Sometimes only WM_NCDESTROY is received in --wid mode
case WM_DESTROY:
if (w32->destroyed)
break;
// If terminate is not set, something else destroyed the window. This
// can also happen in --wid mode when the parent window is destroyed.
if (!w32->terminate)
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
RevokeDragDrop(w32->window);
w32->destroyed = true;
w32->window = NULL;
PostQuitMessage(0);
return 0;
break;
case WM_SYSCOMMAND:
switch (wParam) {
case SC_SCREENSAVE:
@ -901,27 +909,109 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
return DefWindowProcW(hWnd, message, wParam, lParam);
}
static pthread_once_t window_class_init_once = PTHREAD_ONCE_INIT;
static ATOM window_class;
static void register_window_class(void)
{
window_class = RegisterClassExW(&(WNDCLASSEXW) {
.cbSize = sizeof(WNDCLASSEXW),
.style = CS_HREDRAW | CS_VREDRAW,
.lpfnWndProc = WndProc,
.hInstance = HINST_THISCOMPONENT,
.hIcon = LoadIconW(HINST_THISCOMPONENT, L"IDI_ICON1"),
.hCursor = LoadCursor(NULL, IDC_ARROW),
.lpszClassName = L"mpv",
});
}
static ATOM get_window_class(void)
{
pthread_once(&window_class_init_once, register_window_class);
return window_class;
}
static void resize_child_win(HWND parent)
{
// Check if an mpv window is a child of this window. This will not
// necessarily be the case because the hook functions will run for all
// windows on the parent window's thread.
ATOM cls = get_window_class();
HWND child = FindWindowExW(parent, NULL, (LPWSTR)MAKEINTATOM(cls), NULL);
if (!child)
return;
// Make sure the window was created by this instance
if (GetWindowLongPtrW(child, GWLP_HINSTANCE) != (LONG_PTR)HINST_THISCOMPONENT)
return;
// Resize the mpv window to match its parent window's size
RECT rm, rp;
if (!GetClientRect(child, &rm))
return;
if (!GetClientRect(parent, &rp))
return;
if (EqualRect(&rm, &rp))
return;
SetWindowPos(child, NULL, 0, 0, rp.right, rp.bottom, SWP_ASYNCWINDOWPOS |
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
}
static LRESULT CALLBACK parent_win_hook(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode != HC_ACTION)
goto done;
CWPSTRUCT *cwp = (CWPSTRUCT*)lParam;
if (cwp->message != WM_WINDOWPOSCHANGED)
goto done;
resize_child_win(cwp->hwnd);
done:
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
static void CALLBACK parent_evt_hook(HWINEVENTHOOK hWinEventHook, DWORD event,
HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread,
DWORD dwmsEventTime)
{
if (event != EVENT_OBJECT_LOCATIONCHANGE)
return;
if (!hwnd || idObject != OBJID_WINDOW || idChild != CHILDID_SELF)
return;
resize_child_win(hwnd);
}
static void install_parent_hook(struct vo_w32_state *w32)
{
DWORD pid;
DWORD tid = GetWindowThreadProcessId(w32->parent, &pid);
// If the parent lives inside the current process, install a Windows hook
if (pid == GetCurrentProcessId()) {
w32->parent_win_hook = SetWindowsHookExW(WH_CALLWNDPROC,
parent_win_hook, NULL, tid);
} else {
// Otherwise, use a WinEvent hook. These don't seem to be as smooth as
// Windows hooks, but they can be delivered across process boundaries.
w32->parent_evt_hook = SetWinEventHook(
EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE,
NULL, parent_evt_hook, pid, tid, WINEVENT_OUTOFCONTEXT);
}
}
static void remove_parent_hook(struct vo_w32_state *w32)
{
if (w32->parent_win_hook)
UnhookWindowsHookEx(w32->parent_win_hook);
if (w32->parent_evt_hook)
UnhookWinEvent(w32->parent_evt_hook);
}
// Dispatch incoming window events and handle them.
// This returns only when the thread is asked to terminate.
static void run_message_loop(struct vo_w32_state *w32)
{
MSG msg;
while (GetMessageW(&msg, 0, 0, 0) > 0) {
while (GetMessageW(&msg, 0, 0, 0) > 0)
DispatchMessageW(&msg);
if (w32->parent) {
RECT r, rp;
BOOL res = GetClientRect(w32->window, &r);
res = res && GetClientRect(w32->parent, &rp);
if (res && (r.right != rp.right || r.bottom != rp.bottom))
MoveWindow(w32->window, 0, 0, rp.right, rp.bottom, FALSE);
// Window has probably been closed, e.g. due to parent program crash
if (!IsWindow(w32->parent))
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
}
}
// Even if the message loop somehow exits, we still have to respond to
// external requests until termination is requested.
while (!w32->terminate)
@ -1222,33 +1312,26 @@ static void *gui_thread(void *ptr)
thread_disable_ime();
WNDCLASSEXW wcex = {
.cbSize = sizeof wcex,
.style = CS_HREDRAW | CS_VREDRAW,
.lpfnWndProc = WndProc,
.hInstance = HINST_THISCOMPONENT,
.hIcon = LoadIconW(HINST_THISCOMPONENT, L"IDI_ICON1"),
.hCursor = LoadCursor(NULL, IDC_ARROW),
.lpszClassName = classname,
};
RegisterClassExW(&wcex);
w32_thread_context = w32;
if (w32->opts->WinID >= 0)
w32->parent = (HWND)(intptr_t)(w32->opts->WinID);
ATOM cls = get_window_class();
if (w32->parent) {
RECT r;
GetClientRect(w32->parent, &r);
w32->window = CreateWindowExW(WS_EX_NOPARENTNOTIFY, classname,
classname,
w32->window = CreateWindowExW(WS_EX_NOPARENTNOTIFY,
(LPWSTR)MAKEINTATOM(cls), L"mpv",
WS_CHILD | WS_VISIBLE,
0, 0, r.right, r.bottom,
w32->parent, 0, HINST_THISCOMPONENT, NULL);
// Install a hook to get notifications when the parent changes size
if (w32->window)
install_parent_hook(w32);
} else {
w32->window = CreateWindowExW(0, classname,
classname,
w32->window = CreateWindowExW(0, (LPWSTR)MAKEINTATOM(cls), L"mpv",
update_style(w32, 0),
CW_USEDEFAULT, SW_HIDE, 100, 100,
0, 0, HINST_THISCOMPONENT, NULL);
@ -1323,10 +1406,9 @@ done:
MP_VERBOSE(w32, "uninit\n");
if (w32->window) {
RevokeDragDrop(w32->window);
remove_parent_hook(w32);
if (w32->window && !w32->destroyed)
DestroyWindow(w32->window);
}
if (w32->taskbar_list)
ITaskbarList2_Release(w32->taskbar_list);
if (w32->taskbar_list3)