summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--video/out/w32_common.c234
1 files changed, 174 insertions, 60 deletions
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index 8b3fccb714..1f40999b1c 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -18,6 +18,7 @@
#include <stdio.h>
#include <limits.h>
+#include <pthread.h>
#include <assert.h>
#include <windows.h>
#include <windowsx.h>
@@ -34,6 +35,8 @@
#include "w32_common.h"
#include "osdep/io.h"
#include "osdep/w32_keyboard.h"
+#include "misc/dispatch.h"
+#include "misc/rendezvous.h"
#include "talloc.h"
#define WIN_ID_TO_HWND(x) ((HWND)(intptr_t)(x))
@@ -48,6 +51,10 @@ struct vo_w32_state {
struct mp_vo_opts *opts;
struct input_ctx *input_ctx;
+ pthread_t thread;
+ bool terminate;
+ struct mp_dispatch_queue *dispatch; // used to run stuff on the GUI thread
+
HWND window;
// Size and virtual position of the current screen.
@@ -491,6 +498,18 @@ static bool handle_char(struct vo_w32_state *w32, wchar_t wc)
return true;
}
+static void signal_events(struct vo_w32_state *w32, int events)
+{
+ w32->event_flags |= events;
+ mp_input_wakeup(w32->input_ctx);
+}
+
+static void wakeup_gui_thread(void *ctx)
+{
+ struct vo_w32_state *w32 = ctx;
+ PostMessage(w32->window, WM_USER, 0, 0);
+}
+
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
@@ -499,10 +518,14 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
int mouse_button = 0;
switch (message) {
+ case WM_USER:
+ // This message is used to wakeup the GUI thread, see wakeup_gui_thread.
+ mp_dispatch_queue_process(w32->dispatch, 0);
+ break;
case WM_ERASEBKGND: // no need to erase background seperately
return 1;
case WM_PAINT:
- w32->event_flags |= VO_EVENT_EXPOSE;
+ signal_events(w32, VO_EVENT_EXPOSE);
break;
case WM_MOVE: {
POINT p = {0};
@@ -513,13 +536,11 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
break;
}
case WM_SIZE: {
- w32->event_flags |= VO_EVENT_RESIZE;
RECT r;
GetClientRect(w32->window, &r);
w32->dw = r.right;
w32->dh = r.bottom;
- w32->vo->dwidth = w32->dw;
- w32->vo->dheight = w32->dh;
+ signal_events(w32, VO_EVENT_RESIZE);
MP_VERBOSE(w32, "resize window: %d:%d\n", w32->dw, w32->dh);
break;
}
@@ -546,8 +567,10 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
}
break;
case WM_CLOSE:
+ // Don't actually allow it to destroy the window, or whatever else it
+ // is that will make us lose WM_USER wakeups.
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
- break;
+ return 1;
case WM_SYSCOMMAND:
switch (wParam) {
case SC_SCREENSAVE:
@@ -674,33 +697,34 @@ static bool is_key_message(UINT msg)
}
// Dispatch incoming window events and handle them.
-// Currently polls the message queue, until it's empty.
-static int vo_w32_check_events(struct vo_w32_state *w32)
+// This returns only when the thread is asked to terminate.
+static void run_message_loop(struct vo_w32_state *w32)
{
MSG msg;
- w32->event_flags = 0;
-
- while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
+ while (GetMessageW(&msg, 0, 0, 0) > 0) {
// Only send IME messages to TranslateMessage
if (is_key_message(msg.message) && msg.wParam == VK_PROCESSKEY)
TranslateMessage(&msg);
DispatchMessageW(&msg);
- }
- if (w32->opts->WinID >= 0) {
- HWND parent = WIN_ID_TO_HWND(w32->opts->WinID);
- RECT r, rp;
- BOOL res = GetClientRect(w32->window, &r);
- res = res && GetClientRect(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(parent))
- mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
+ if (w32->opts->WinID >= 0) {
+ HWND parent = WIN_ID_TO_HWND(w32->opts->WinID);
+ RECT r, rp;
+ BOOL res = GetClientRect(w32->window, &r);
+ res = res && GetClientRect(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(parent))
+ mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
+ }
}
- return w32->event_flags;
+ // Even if the message loop somehow exits, we still have to respond to
+ // external requests until termination is requested.
+ while (!w32->terminate)
+ mp_dispatch_queue_process(w32->dispatch, 1000);
}
static BOOL CALLBACK mon_enum(HMONITOR hmon, HDC hdc, LPRECT r, LPARAM p)
@@ -824,8 +848,6 @@ static int reinit_window_state(struct vo_w32_state *w32)
w32->window_y = w32->prev_y;
}
}
- w32->vo->dwidth = w32->dw;
- w32->vo->dheight = w32->dh;
r.left = w32->window_x;
r.right = r.left + w32->dw;
@@ -849,17 +871,25 @@ static int reinit_window_state(struct vo_w32_state *w32)
SetWindowPos(w32->window, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
+ signal_events(w32, VO_EVENT_RESIZE);
+
return 1;
}
-// Resize the window. On the first non-VOFLAG_HIDDEN call, it's also made visible.
-int vo_w32_config(struct vo *vo, uint32_t flags)
+static void gui_thread_reconfig(void *ptr)
{
- struct vo_w32_state *w32 = vo->w32;
+ void **p = ptr;
+ struct vo_w32_state *w32 = p[0];
+ uint32_t flags = *(uint32_t *)p[1];
+ int *res = p[2];
+
+ struct vo *vo = w32->vo;
// we already have a fully initialized window, so nothing needs to be done
- if (flags & VOFLAG_HIDDEN)
- return 1;
+ if (flags & VOFLAG_HIDDEN) {
+ *res = 1;
+ return;
+ }
struct vo_win_geometry geo;
vo_calc_window_geometry(vo, &w32->screenrc, &geo);
@@ -899,22 +929,24 @@ int vo_w32_config(struct vo *vo, uint32_t flags)
vo->dheight = r.bottom;
}
- return reinit_window_state(w32);
+ *res = reinit_window_state(w32);
}
-// Returns: 1 = Success, 0 = Failure
-int vo_w32_init(struct vo *vo)
+// Resize the window. On the first non-VOFLAG_HIDDEN call, it's also made visible.
+int vo_w32_config(struct vo *vo, uint32_t flags)
{
- assert(!vo->w32);
+ struct vo_w32_state *w32 = vo->w32;
+ int r;
+ void *p[] = {w32, &flags, &r};
+ mp_dispatch_run(w32->dispatch, gui_thread_reconfig, p);
+ return r;
+}
- struct vo_w32_state *w32 = talloc_ptrtype(vo, w32);
- vo->w32 = w32;
- *w32 = (struct vo_w32_state){
- .log = mp_log_new(w32, vo->log, "win32"),
- .vo = vo,
- .opts = vo->opts,
- .input_ctx = vo->input_ctx,
- };
+static void *gui_thread(void *ptr)
+{
+ struct vo_w32_state *w32 = ptr;
+ bool ole_ok = false;
+ int res = 0;
HINSTANCE hInstance = GetModuleHandleW(NULL);
@@ -930,7 +962,7 @@ int vo_w32_init(struct vo *vo)
if (!RegisterClassExW(&wcex)) {
MP_ERR(w32, "unable to register window class!\n");
- return 0;
+ goto done;
}
w32_thread_context = w32;
@@ -954,7 +986,7 @@ int vo_w32_init(struct vo *vo)
if (!w32->window) {
MP_ERR(w32, "unable to create window!\n");
- return 0;
+ goto done;
}
if (OleInitialize(NULL) == S_OK) {
@@ -962,6 +994,7 @@ int vo_w32_init(struct vo *vo)
DropTarget* dropTarget = talloc(NULL, DropTarget);
DropTarget_Init(dropTarget, w32);
RegisterDragDrop(w32->window, &dropTarget->iface);
+ ole_ok = true;
}
w32->tracking = FALSE;
@@ -976,12 +1009,66 @@ int vo_w32_init(struct vo *vo)
w32->cursor_visible = true;
- // we don't have proper event handling
- vo->wakeup_period = 0.02;
-
updateScreenProperties(w32);
+ mp_dispatch_set_wakeup_fn(w32->dispatch, wakeup_gui_thread, w32);
+
+ // Microsoft-recommended way to create a message queue.
+ // Needed so that initial WM_USER wakeups are not lost.
+ PeekMessage(&(MSG){0}, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+
+ res = 1;
+done:
+
+ mp_rendezvous(w32, res); // init barrier
+
+ // This blocks until the GUI thread is to be exited.
+ if (res)
+ run_message_loop(w32);
+
+ MP_VERBOSE(w32, "uninit\n");
+
+ if (w32->window) {
+ RevokeDragDrop(w32->window);
+ DestroyWindow(w32->window);
+ }
+ if (ole_ok)
+ OleUninitialize();
+ SetThreadExecutionState(ES_CONTINUOUS);
+ UnregisterClassW(classname, 0);
+
+ w32_thread_context = NULL;
+ return NULL;
+}
+
+// Returns: 1 = Success, 0 = Failure
+int vo_w32_init(struct vo *vo)
+{
+ assert(!vo->w32);
+
+ struct vo_w32_state *w32 = talloc_ptrtype(vo, w32);
+ *w32 = (struct vo_w32_state){
+ .log = mp_log_new(w32, vo->log, "win32"),
+ .vo = vo,
+ .opts = vo->opts,
+ .input_ctx = vo->input_ctx,
+ .dispatch = mp_dispatch_create(w32),
+ };
+ vo->w32 = w32;
+
+ if (pthread_create(&w32->thread, NULL, gui_thread, w32))
+ goto fail;
+
+ if (!mp_rendezvous(w32, 0)) { // init barrier
+ pthread_join(w32->thread, NULL);
+ goto fail;
+ }
+
return 1;
+fail:
+ talloc_free(w32);
+ vo->w32 = NULL;
+ return 0;
}
static bool vo_w32_is_cursor_in_client(struct vo_w32_state *w32)
@@ -990,13 +1077,12 @@ static bool vo_w32_is_cursor_in_client(struct vo_w32_state *w32)
return SendMessage(w32->window, WM_NCHITTEST, 0, pos) == HTCLIENT;
}
-int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
+static int gui_thread_control(struct vo_w32_state *w32, int *events,
+ int request, void *arg)
{
- struct vo_w32_state *w32 = vo->w32;
+ *events |= w32->event_flags;
+ w32->event_flags = 0;
switch (request) {
- case VOCTRL_CHECK_EVENTS:
- *events |= vo_w32_check_events(w32);
- return VO_TRUE;
case VOCTRL_FULLSCREEN:
if (w32->opts->fullscreen != w32->current_fs)
reinit_window_state(w32);
@@ -1008,7 +1094,7 @@ int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
return VO_TRUE;
case VOCTRL_BORDER:
w32->opts->border = !w32->opts->border;
- reinit_window_state(vo->w32);
+ reinit_window_state(w32);
*events |= VO_EVENT_RESIZE;
return VO_TRUE;
case VOCTRL_GET_WINDOW_SIZE: {
@@ -1066,19 +1152,47 @@ int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
return VO_NOTIMPL;
}
+static void do_control(void *ptr)
+{
+ void **p = ptr;
+ struct vo_w32_state *w32 = p[0];
+ int *events = p[1];
+ int request = *(int *)p[2];
+ void *arg = p[3];
+ int *ret = p[4];
+ *ret = gui_thread_control(w32, events, request, arg);
+ // Safe access, since caller (owner of vo) is blocked.
+ if (*events & VO_EVENT_RESIZE) {
+ w32->vo->dwidth = w32->dw;
+ w32->vo->dheight = w32->dh;
+ }
+}
+
+int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
+{
+ struct vo_w32_state *w32 = vo->w32;
+ int r;
+ void *p[] = {w32, events, &request, arg, &r};
+ mp_dispatch_run(w32->dispatch, do_control, p);
+ return r;
+}
+
+static void do_terminate(void *ptr)
+{
+ struct vo_w32_state *w32 = ptr;
+ w32->terminate = true;
+ PostQuitMessage(0);
+}
+
void vo_w32_uninit(struct vo *vo)
{
struct vo_w32_state *w32 = vo->w32;
if (!w32)
return;
- MP_VERBOSE(w32, "uninit\n");
+ mp_dispatch_run(w32->dispatch, do_terminate, w32);
+ pthread_join(w32->thread, NULL);
- RevokeDragDrop(w32->window);
- OleUninitialize();
- SetThreadExecutionState(ES_CONTINUOUS);
- DestroyWindow(w32->window);
- UnregisterClassW(classname, 0);
w32_thread_context = NULL;
talloc_free(w32);
vo->w32 = NULL;
@@ -1087,5 +1201,5 @@ void vo_w32_uninit(struct vo *vo)
HWND vo_w32_hwnd(struct vo *vo)
{
struct vo_w32_state *w32 = vo->w32;
- return w32->window;
+ return w32->window; // immutable, so no synchronization needed
}