summaryrefslogtreecommitdiffstats
path: root/video/out/w32_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/w32_common.c')
-rw-r--r--video/out/w32_common.c457
1 files changed, 355 insertions, 102 deletions
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index e6a4670..36f48b9 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -40,6 +40,7 @@
#include "w32_common.h"
#include "win32/displayconfig.h"
#include "win32/droptarget.h"
+#include "win32/menu.h"
#include "osdep/io.h"
#include "osdep/threads.h"
#include "osdep/w32_keyboard.h"
@@ -58,10 +59,17 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
+#ifndef DWMWA_VISIBLE_FRAME_BORDER_THICKNESS
+#define DWMWA_VISIBLE_FRAME_BORDER_THICKNESS 37
+#endif
-//Older MinGW compatibility
+#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
#define DWMWA_WINDOW_CORNER_PREFERENCE 33
+#endif
+
+#ifndef DWMWA_SYSTEMBACKDROP_TYPE
#define DWMWA_SYSTEMBACKDROP_TYPE 38
+#endif
#ifndef DPI_ENUMS_DECLARED
typedef enum MONITOR_DPI_TYPE {
@@ -75,10 +83,12 @@ typedef enum MONITOR_DPI_TYPE {
#define rect_w(r) ((r).right - (r).left)
#define rect_h(r) ((r).bottom - (r).top)
+#define WM_SHOWMENU (WM_USER + 1)
+
struct w32_api {
HRESULT (WINAPI *pGetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
- BOOL (WINAPI *pImmDisableIME)(DWORD);
BOOL (WINAPI *pAdjustWindowRectExForDpi)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
+ int (WINAPI *pGetSystemMetricsForDpi)(int nIndex, UINT dpi);
BOOLEAN (WINAPI *pShouldAppsUseDarkMode)(void);
DWORD (WINAPI *pSetPreferredAppMode)(DWORD mode);
};
@@ -102,6 +112,8 @@ struct vo_w32_state {
HHOOK parent_win_hook;
HWINEVENTHOOK parent_evt_hook;
+ struct menu_ctx *menu_ctx;
+
HMONITOR monitor; // Handle of the current screen
char *color_profile; // Path of the current screen's color profile
@@ -132,7 +144,7 @@ struct vo_w32_state {
atomic_uint event_flags;
BOOL tracking;
- TRACKMOUSEEVENT trackEvent;
+ TRACKMOUSEEVENT track_event;
int mouse_x;
int mouse_y;
@@ -156,8 +168,8 @@ struct vo_w32_state {
ITaskbarList2 *taskbar_list;
ITaskbarList3 *taskbar_list3;
- UINT tbtnCreatedMsg;
- bool tbtnCreated;
+ UINT tbtn_created_msg;
+ bool tbtn_created;
struct voctrl_playback_state current_pstate;
@@ -181,8 +193,21 @@ struct vo_w32_state {
HANDLE avrt_handle;
bool cleared;
+ bool dragging;
+ bool start_dragging;
+ BOOL win_arranging;
+
+ bool conversion_mode_init;
+ bool unmaximize;
};
+static inline int get_system_metrics(struct vo_w32_state *w32, int metric)
+{
+ return w32->api.pGetSystemMetricsForDpi
+ ? w32->api.pGetSystemMetricsForDpi(metric, w32->dpi)
+ : GetSystemMetrics(metric);
+}
+
static void adjust_window_rect(struct vo_w32_state *w32, HWND hwnd, RECT *rc)
{
if (!w32->opts->border)
@@ -197,13 +222,67 @@ static void adjust_window_rect(struct vo_w32_state *w32, HWND hwnd, RECT *rc)
}
}
+static bool check_windows10_build(DWORD build)
+{
+ OSVERSIONINFOEXW osvi = {
+ .dwOSVersionInfoSize = sizeof(osvi),
+ .dwMajorVersion = HIBYTE(_WIN32_WINNT_WIN10),
+ .dwMinorVersion = LOBYTE(_WIN32_WINNT_WIN10),
+ .dwBuildNumber = build,
+ };
+
+ DWORD type = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER;
+
+ ULONGLONG mask = 0;
+ mask = VerSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
+ mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+ mask = VerSetConditionMask(mask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
+
+ return VerifyVersionInfoW(&osvi, type, mask);
+}
+
+// Get adjusted title bar height, only relevant for --title-bar=no
+static int get_title_bar_height(struct vo_w32_state *w32)
+{
+ assert(!w32->opts->title_bar && w32->opts->border);
+ UINT visible_border = 0;
+ // Only available on Windows 11, check in case it's backported and breaks
+ // WM_NCCALCSIZE exception for Windows 10.
+ if (check_windows10_build(22000)) {
+ DwmGetWindowAttribute(w32->window, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,
+ &visible_border, sizeof(visible_border));
+ }
+ int top_bar = IsMaximized(w32->window)
+ ? get_system_metrics(w32, SM_CYFRAME) +
+ get_system_metrics(w32, SM_CXPADDEDBORDER)
+ : visible_border;
+ return top_bar;
+}
+
static void add_window_borders(struct vo_w32_state *w32, HWND hwnd, RECT *rc)
{
RECT win = *rc;
adjust_window_rect(w32, hwnd, rc);
// Adjust for title bar height that will be hidden in WM_NCCALCSIZE
- if (w32->opts->border && !w32->opts->title_bar && !w32->current_fs)
- rc->top -= rc->top - win.top;
+ // Keep the frame border. On Windows 10 the top border is not retained.
+ // It appears that DWM draws the title bar with its full height, extending
+ // outside the window area. Essentially, there is a bug in DWM, preventing
+ // the adjustment of the title bar height. This issue occurs when both the
+ // top and left client areas are non-zero in WM_NCCALCSIZE. If the left NC
+ // area is set to 0, the title bar is drawn correctly with the adjusted
+ // height. To mitigate this problem, set the top NC area to zero. The issue
+ // doesn't happen on Windows 11 or when DWM NC drawing is disabled with
+ // DWMWA_NCRENDERING_POLICY. We aim to avoid the manual drawing the border
+ // and want the DWM look and feel, so skip the top border on Windows 10.
+ // Also DWMWA_VISIBLE_FRAME_BORDER_THICKNESS is available only on Windows 11,
+ // so it would be hard to guess this size correctly on Windows 10 anyway.
+ if (w32->opts->border && !w32->opts->title_bar && !w32->current_fs &&
+ (GetWindowLongPtrW(w32->window, GWL_STYLE) & WS_CAPTION))
+ {
+ if (!check_windows10_build(22000) && !IsMaximized(w32->window))
+ *rc = win;
+ rc->top = win.top - get_title_bar_height(w32);
+ }
}
// basically a reverse AdjustWindowRect (win32 doesn't appear to have this)
@@ -226,13 +305,13 @@ static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y)
if (!GetWindowRect(w32->window, &rc))
return HTNOWHERE;
- POINT frame = {GetSystemMetrics(SM_CXSIZEFRAME),
- GetSystemMetrics(SM_CYSIZEFRAME)};
+ POINT frame = {get_system_metrics(w32, SM_CXSIZEFRAME),
+ get_system_metrics(w32, SM_CYSIZEFRAME)};
if (w32->opts->border) {
- frame.x += GetSystemMetrics(SM_CXPADDEDBORDER);
- frame.y += GetSystemMetrics(SM_CXPADDEDBORDER);
+ frame.x += get_system_metrics(w32, SM_CXPADDEDBORDER);
+ frame.y += get_system_metrics(w32, SM_CXPADDEDBORDER);
if (!w32->opts->title_bar)
- rc.top -= GetSystemMetrics(SM_CXPADDEDBORDER);
+ rc.top -= get_system_metrics(w32, SM_CXPADDEDBORDER);
}
InflateRect(&rc, -frame.x, -frame.y);
@@ -344,8 +423,8 @@ static void clear_keyboard_buffer(void)
// Use the method suggested by Michael Kaplan to clear any pending dead
// keys from the current keyboard layout. See:
- // https://web.archive.org/web/20101004154432/http://blogs.msdn.com/b/michkap/archive/2006/04/06/569632.aspx
- // https://web.archive.org/web/20100820152419/http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx
+ // <https://web.archive.org/web/20101004154432/http://blogs.msdn.com/b/michkap/archive/2006/04/06/569632.aspx>
+ // <https://web.archive.org/web/20100820152419/http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx>
do {
ret = ToUnicode(vkey, scancode, keys, buf, MP_ARRAY_SIZE(buf), 0);
} while (ret < 0);
@@ -356,7 +435,7 @@ static int to_unicode(UINT vkey, UINT scancode, const BYTE keys[256])
// This wraps ToUnicode to be stateless and to return only one character
// Make the buffer 10 code units long to be safe, same as here:
- // https://web.archive.org/web/20101013215215/http://blogs.msdn.com/b/michkap/archive/2006/03/24/559169.aspx
+ // <https://web.archive.org/web/20101013215215/http://blogs.msdn.com/b/michkap/archive/2006/03/24/559169.aspx>
wchar_t buf[10] = { 0 };
// Dead keys aren't useful for key shortcuts, so clear the keyboard state
@@ -428,10 +507,6 @@ static bool handle_appcommand(struct vo_w32_state *w32, UINT cmd)
static void handle_key_down(struct vo_w32_state *w32, UINT vkey, UINT scancode)
{
- // Ignore key repeat
- if (scancode & KF_REPEAT)
- return;
-
int mpkey = mp_w32_vkey_to_mpkey(vkey, scancode & KF_EXTENDED);
if (!mpkey) {
mpkey = decode_key(w32, vkey, scancode & (0xff | KF_EXTENDED));
@@ -439,7 +514,8 @@ static void handle_key_down(struct vo_w32_state *w32, UINT vkey, UINT scancode)
return;
}
- mp_input_put_key(w32->input_ctx, mpkey | mod_state(w32) | MP_KEY_STATE_DOWN);
+ int state = w32->opts->native_keyrepeat ? 0 : MP_KEY_STATE_DOWN;
+ mp_input_put_key(w32->input_ctx, mpkey | mod_state(w32) | state);
}
static void handle_key_up(struct vo_w32_state *w32, UINT vkey, UINT scancode)
@@ -456,9 +532,9 @@ static void handle_key_up(struct vo_w32_state *w32, UINT vkey, UINT scancode)
}
}
-static bool handle_char(struct vo_w32_state *w32, wchar_t wc)
+static bool handle_char(struct vo_w32_state *w32, WPARAM wc, bool decode)
{
- int c = decode_utf16(w32, wc);
+ int c = decode ? decode_utf16(w32, wc) : wc;
if (c == 0)
return true;
@@ -469,23 +545,33 @@ static bool handle_char(struct vo_w32_state *w32, wchar_t wc)
return true;
}
+static void begin_dragging(struct vo_w32_state *w32)
+{
+ if (w32->current_fs ||
+ mp_input_test_dragging(w32->input_ctx, w32->mouse_x, w32->mouse_y))
+ return;
+ // Window dragging hack
+ ReleaseCapture();
+ // The dragging model loop is entered at SendMessage() here.
+ // Unfortunately, the w32->current_fs value is stale because the
+ // input is handled in a different thread, and we cannot wait for
+ // an up-to-date value before entering the model loop if dragging
+ // needs to be kept resonsive.
+ // Workaround this by intercepting the loop in the WM_MOVING message,
+ // where the up-to-date value is available.
+ SystemParametersInfoW(SPI_GETWINARRANGING, 0, &w32->win_arranging, 0);
+ w32->dragging = true;
+ SendMessage(w32->window, WM_NCLBUTTONDOWN, HTCAPTION, 0);
+ w32->dragging = false;
+ SystemParametersInfoW(SPI_SETWINARRANGING, w32->win_arranging, 0, 0);
+
+ mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL);
+}
+
static bool handle_mouse_down(struct vo_w32_state *w32, int btn, int x, int y)
{
btn |= mod_state(w32);
mp_input_put_key(w32->input_ctx, btn | MP_KEY_STATE_DOWN);
-
- if (btn == MP_MBTN_LEFT && !w32->current_fs &&
- !mp_input_test_dragging(w32->input_ctx, x, y))
- {
- // Window dragging hack
- ReleaseCapture();
- SendMessage(w32->window, WM_NCLBUTTONDOWN, HTCAPTION, 0);
- mp_input_put_key(w32->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP);
-
- // Indicate the message was handled, so DefWindowProc won't be called
- return true;
- }
-
SetCapture(w32->window);
return false;
}
@@ -566,18 +652,20 @@ static double get_refresh_rate_from_gdi(const wchar_t *device)
static char *get_color_profile(void *ctx, const wchar_t *device)
{
char *name = NULL;
+ wchar_t *wname = NULL;
HDC ic = CreateICW(device, NULL, NULL, NULL);
if (!ic)
goto done;
- wchar_t wname[MAX_PATH + 1];
- if (!GetICMProfileW(ic, &(DWORD){ MAX_PATH }, wname))
+ wname = talloc_array(NULL, wchar_t, MP_PATH_MAX);
+ if (!GetICMProfileW(ic, &(DWORD){ MP_PATH_MAX - 1 }, wname))
goto done;
name = mp_to_utf8(ctx, wname);
done:
if (ic)
DeleteDC(ic);
+ talloc_free(wname);
return name;
}
@@ -603,7 +691,7 @@ static void update_dpi(struct vo_w32_state *w32)
}
w32->dpi = dpi;
- w32->dpi_scale = w32->opts->hidpi_window_scale ? w32->dpi / 96.0 : 1.0;
+ w32->dpi_scale = w32->dpi / 96.0;
signal_events(w32, VO_EVENT_DPI);
}
@@ -660,7 +748,7 @@ static void update_playback_state(struct vo_w32_state *w32)
{
struct voctrl_playback_state *pstate = &w32->current_pstate;
- if (!w32->taskbar_list3 || !w32->tbtnCreated)
+ if (!w32->taskbar_list3 || !w32->tbtn_created)
return;
if (!pstate->playing || !pstate->taskbar_progress) {
@@ -743,10 +831,10 @@ static RECT get_screen_area(struct vo_w32_state *w32)
{
// Handle --fs-screen=all
if (w32->current_fs && w32->opts->fsscreen_id == -2) {
- const int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
- const int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
- return (RECT) { x, y, x + GetSystemMetrics(SM_CXVIRTUALSCREEN),
- y + GetSystemMetrics(SM_CYVIRTUALSCREEN) };
+ const int x = get_system_metrics(w32, SM_XVIRTUALSCREEN);
+ const int y = get_system_metrics(w32, SM_YVIRTUALSCREEN);
+ return (RECT) { x, y, x + get_system_metrics(w32, SM_CXVIRTUALSCREEN),
+ y + get_system_metrics(w32, SM_CYVIRTUALSCREEN) };
}
return get_monitor_info(w32).rcMonitor;
}
@@ -842,6 +930,13 @@ static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
return true;
}
+static bool is_high_contrast(void)
+{
+ HIGHCONTRAST hc = {sizeof(hc)};
+ SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0);
+ return hc.dwFlags & HCF_HIGHCONTRASTON;
+}
+
static DWORD update_style(struct vo_w32_state *w32, DWORD style)
{
const DWORD NO_FRAME = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_THICKFRAME;
@@ -853,17 +948,12 @@ static DWORD update_style(struct vo_w32_state *w32, DWORD style)
style |= FULLSCREEN;
} else {
style |= w32->opts->border ? FRAME : NO_FRAME;
+ if (!w32->opts->title_bar && is_high_contrast())
+ style &= ~WS_CAPTION;
}
return style;
}
-static LONG get_title_bar_height(struct vo_w32_state *w32)
-{
- RECT rc = {0};
- adjust_window_rect(w32, w32->window, &rc);
- return -rc.top;
-}
-
static void update_window_style(struct vo_w32_state *w32)
{
if (w32->parent)
@@ -1006,14 +1096,13 @@ static void update_minimized_state(struct vo_w32_state *w32)
}
}
-static void update_maximized_state(struct vo_w32_state *w32)
+static void update_maximized_state(struct vo_w32_state *w32, bool leaving_fullscreen)
{
if (w32->parent)
return;
- // Don't change the maximized state in fullscreen for now. In future, this
- // should be made to apply the maximized state on leaving fullscreen.
- if (w32->current_fs)
+ // Apply the maximized state on leaving fullscreen.
+ if (w32->current_fs && !leaving_fullscreen)
return;
WINDOWPLACEMENT wp = { .length = sizeof wp };
@@ -1065,11 +1154,24 @@ static void update_window_state(struct vo_w32_state *w32)
wr.left, wr.top, rect_w(wr), rect_h(wr),
SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+ // Unmaximize the window if a size change is requested because SetWindowPos
+ // doesn't change the window maximized state.
+ // ShowWindow(SW_SHOWNOACTIVATE) can't be used here because it tries to
+ // "restore" the window to its size before it's maximized.
+ if (w32->unmaximize) {
+ WINDOWPLACEMENT wp = { .length = sizeof wp };
+ GetWindowPlacement(w32->window, &wp);
+ wp.showCmd = SW_SHOWNOACTIVATE;
+ wp.rcNormalPosition = wr;
+ SetWindowPlacement(w32->window, &wp);
+ w32->unmaximize = false;
+ }
+
// Show the window if it's not yet visible
if (!is_visible(w32->window)) {
if (w32->opts->window_minimized) {
ShowWindow(w32->window, SW_SHOWMINNOACTIVE);
- update_maximized_state(w32); // Set the WPF_RESTORETOMAXIMIZED flag
+ update_maximized_state(w32, false); // Set the WPF_RESTORETOMAXIMIZED flag
} else if (w32->opts->window_maximized) {
ShowWindow(w32->window, SW_SHOWMAXIMIZED);
} else {
@@ -1166,13 +1268,9 @@ static void update_dark_mode(const struct vo_w32_state *w32)
if (w32->api.pSetPreferredAppMode)
w32->api.pSetPreferredAppMode(1); // allow dark mode
- HIGHCONTRAST hc = {sizeof(hc)};
- SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0);
- bool high_contrast = hc.dwFlags & HCF_HIGHCONTRASTON;
-
// if pShouldAppsUseDarkMode is not available, just assume it to be true
- const BOOL use_dark_mode = !high_contrast && (!w32->api.pShouldAppsUseDarkMode ||
- w32->api.pShouldAppsUseDarkMode());
+ const BOOL use_dark_mode = !is_high_contrast() && (!w32->api.pShouldAppsUseDarkMode ||
+ w32->api.pShouldAppsUseDarkMode());
SetWindowTheme(w32->window, use_dark_mode ? L"DarkMode_Explorer" : L"", NULL);
@@ -1190,6 +1288,37 @@ static void update_backdrop(const struct vo_w32_state *w32)
&backdropType, sizeof(backdropType));
}
+static void update_cursor_passthrough(const struct vo_w32_state *w32)
+{
+ if (w32->parent)
+ return;
+
+ LONG_PTR exstyle = GetWindowLongPtrW(w32->window, GWL_EXSTYLE);
+ if (exstyle) {
+ if (w32->opts->cursor_passthrough) {
+ SetWindowLongPtrW(w32->window, GWL_EXSTYLE, exstyle | WS_EX_LAYERED | WS_EX_TRANSPARENT);
+ // This is required, otherwise the titlebar disappears.
+ SetLayeredWindowAttributes(w32->window, 0, 255, LWA_ALPHA);
+ } else {
+ SetWindowLongPtrW(w32->window, GWL_EXSTYLE, exstyle & ~(WS_EX_LAYERED | WS_EX_TRANSPARENT));
+ }
+ }
+}
+
+static void set_ime_conversion_mode(const struct vo_w32_state *w32, DWORD mode)
+{
+ if (w32->parent)
+ return;
+
+ HIMC imc = ImmGetContext(w32->window);
+ if (imc) {
+ DWORD sentence_mode;
+ if (ImmGetConversionStatus(imc, NULL, &sentence_mode))
+ ImmSetConversionStatus(imc, mode, sentence_mode);
+ ImmReleaseContext(w32->window, imc);
+ }
+}
+
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
@@ -1216,6 +1345,12 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
mp_dispatch_queue_process(w32->dispatch, 0);
w32->in_dispatch = false;
}
+ // Start window dragging if the flag is set by the voctrl.
+ // This is processed here to avoid blocking the dispatch queue.
+ if (w32->start_dragging) {
+ w32->start_dragging = false;
+ begin_dragging(w32);
+ }
switch (message) {
case WM_ERASEBKGND:
@@ -1241,6 +1376,18 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
case WM_MOVING: {
w32->moving = true;
RECT *rc = (RECT*)lParam;
+ // Prevent the window from being moved if the window dragging hack
+ // is active, and the window is currently in fullscreen.
+ if (w32->dragging && w32->current_fs) {
+ // Temporarily disable window arrangement to prevent aero shake
+ // from being activated. The original system setting will be restored
+ // after the dragging hack ends.
+ if (w32->win_arranging) {
+ SystemParametersInfoW(SPI_SETWINARRANGING, FALSE, 0, 0);
+ }
+ *rc = w32->windowrc;
+ return TRUE;
+ }
if (snap_to_screen_edges(w32, rc))
return TRUE;
break;
@@ -1344,6 +1491,14 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
w32->window = NULL;
PostQuitMessage(0);
break;
+ case WM_COMMAND: {
+ const char *cmd = mp_win32_menu_get_cmd(w32->menu_ctx, LOWORD(wParam));
+ if (cmd) {
+ mp_cmd_t *cmdt = mp_input_parse_cmd(w32->input_ctx, bstr0(cmd), "");
+ mp_input_queue_cmd(w32->input_ctx, cmdt);
+ }
+ break;
+ }
case WM_SYSCOMMAND:
switch (wParam & 0xFFF0) {
case SC_SCREENSAVE:
@@ -1404,9 +1559,16 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
break;
case WM_CHAR:
case WM_SYSCHAR:
- if (handle_char(w32, wParam))
+ if (handle_char(w32, wParam, true))
return 0;
break;
+ case WM_UNICHAR:
+ if (wParam == UNICODE_NOCHAR) {
+ return TRUE;
+ } else if (handle_char(w32, wParam, false)) {
+ return 0;
+ }
+ break;
case WM_KILLFOCUS:
mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL);
w32->focused = false;
@@ -1431,12 +1593,13 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
break;
case WM_MOUSEMOVE: {
if (!w32->tracking) {
- w32->tracking = TrackMouseEvent(&w32->trackEvent);
+ w32->tracking = TrackMouseEvent(&w32->track_event);
mp_input_put_key(w32->input_ctx, MP_KEY_MOUSE_ENTER);
}
// Windows can send spurious mouse events, which would make the mpv
// core unhide the mouse cursor on completely unrelated events. See:
- // https://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx
+ // <https://web.archive.org/web/20100821161603/
+ // https://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx>
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
if (x != w32->mouse_x || y != w32->mouse_y) {
@@ -1480,16 +1643,18 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
handle_mouse_down(w32,
HIWORD(wParam) == 1 ? MP_MBTN_BACK : MP_MBTN_FORWARD,
GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
- break;
+ return TRUE;
case WM_XBUTTONUP:
handle_mouse_up(w32,
HIWORD(wParam) == 1 ? MP_MBTN_BACK : MP_MBTN_FORWARD);
- break;
+ return TRUE;
case WM_DISPLAYCHANGE:
force_update_display_info(w32);
break;
case WM_SETTINGCHANGE:
update_dark_mode(w32);
+ update_window_style(w32);
+ update_window_state(w32);
break;
case WM_NCCALCSIZE:
if (!w32->opts->border)
@@ -1497,15 +1662,51 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
// Apparently removing WS_CAPTION disables some window animation, instead
// just reduce non-client size to remove title bar.
if (wParam && lParam && w32->opts->border && !w32->opts->title_bar &&
- !w32->current_fs && !w32->parent)
+ !w32->current_fs && !w32->parent &&
+ (GetWindowLongPtrW(w32->window, GWL_STYLE) & WS_CAPTION))
{
- ((LPNCCALCSIZE_PARAMS) lParam)->rgrc[0].top -= get_title_bar_height(w32);
+ // Remove all NC area on Windows 10 due to inability to control the
+ // top bar height before Windows 11.
+ if (!check_windows10_build(22000) && !IsMaximized(w32->window))
+ return 0;
+ RECT r = {0};
+ adjust_window_rect(w32, w32->window, &r);
+ NCCALCSIZE_PARAMS *p = (LPNCCALCSIZE_PARAMS)lParam;
+ p->rgrc[0].top += r.top + get_title_bar_height(w32);
+ }
+ break;
+ case WM_IME_STARTCOMPOSITION: {
+ HIMC imc = ImmGetContext(w32->window);
+ if (imc) {
+ COMPOSITIONFORM cf = {.dwStyle = CFS_POINT, .ptCurrentPos = {0, 0}};
+ ImmSetCompositionWindow(imc, &cf);
+ ImmReleaseContext(w32->window, imc);
}
break;
}
+ case WM_CREATE:
+ // The IME can only be changed to alphanumeric input after it's initialized.
+ // Unfortunately, there is no way to know when this happens, as
+ // none of the WM_CREATE, WM_INPUTLANGCHANGE, or WM_IME_* messages work.
+ // This works if the IME is initialized within a short time after
+ // the window is created. Otherwise, fallback to setting alphanumeric mode on
+ // the first keypress.
+ SetTimer(w32->window, (UINT_PTR)WM_CREATE, 250, NULL);
+ break;
+ case WM_TIMER:
+ if (wParam == WM_CREATE) {
+ // Default to alphanumeric input when the IME is first initialized.
+ set_ime_conversion_mode(w32, IME_CMODE_ALPHANUMERIC);
+ return 0;
+ }
+ break;
+ case WM_SHOWMENU:
+ mp_win32_menu_show(w32->menu_ctx, w32->window);
+ break;
+ }
- if (message == w32->tbtnCreatedMsg) {
- w32->tbtnCreated = true;
+ if (message == w32->tbtn_created_msg) {
+ w32->tbtn_created = true;
update_playback_state(w32);
return 0;
}
@@ -1609,13 +1810,31 @@ static void remove_parent_hook(struct vo_w32_state *w32)
UnhookWinEvent(w32->parent_evt_hook);
}
+static bool is_key_message(UINT msg)
+{
+ return msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN ||
+ msg == WM_KEYUP || msg == WM_SYSKEYUP;
+}
+
// 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 (!w32->destroyed && GetMessageW(&msg, 0, 0, 0) > 0) {
+ // Change the conversion mode on the first keypress, in case the timer
+ // solution fails. Note that this leaves the mode indicator in the language
+ // bar showing the original mode until a key is pressed.
+ if (is_key_message(msg.message) && !w32->conversion_mode_init) {
+ set_ime_conversion_mode(w32, IME_CMODE_ALPHANUMERIC);
+ w32->conversion_mode_init = true;
+ KillTimer(w32->window, (UINT_PTR)WM_CREATE);
+ }
+ // Only send IME messages to TranslateMessage
+ if (is_key_message(msg.message) && msg.wParam == VK_PROCESSKEY)
+ TranslateMessage(&msg);
DispatchMessageW(&msg);
+ }
// Even if the message loop somehow exits, we still have to respond to
// external requests until termination is requested.
@@ -1623,9 +1842,8 @@ static void run_message_loop(struct vo_w32_state *w32)
mp_dispatch_queue_process(w32->dispatch, 1000);
}
-static void gui_thread_reconfig(void *ptr)
+static void window_reconfig(struct vo_w32_state *w32, bool force)
{
- struct vo_w32_state *w32 = ptr;
struct vo *vo = w32->vo;
RECT r = get_working_area(w32);
@@ -1648,14 +1866,14 @@ static void gui_thread_reconfig(void *ptr)
vo_calc_window_geometry3(vo, &screen, &mon, w32->dpi_scale, &geo);
vo_apply_window_geometry(vo, &geo);
- bool reset_size = (w32->o_dwidth != vo->dwidth ||
+ bool reset_size = ((w32->o_dwidth != vo->dwidth ||
w32->o_dheight != vo->dheight) &&
- w32->opts->auto_window_resize;
+ w32->opts->auto_window_resize) || force;
w32->o_dwidth = vo->dwidth;
w32->o_dheight = vo->dheight;
- if (!w32->parent && !w32->window_bounds_initialized) {
+ if (!w32->parent && (!w32->window_bounds_initialized || force)) {
SetRect(&w32->windowrc, geo.win.x0, geo.win.y0,
geo.win.x0 + vo->dwidth, geo.win.y0 + vo->dheight);
w32->prev_windowrc = w32->windowrc;
@@ -1686,6 +1904,11 @@ finish:
reinit_window_state(w32);
}
+static void gui_thread_reconfig(void *ptr)
+{
+ window_reconfig(ptr, false);
+}
+
// Resize the window. On the first call, it's also made visible.
void vo_w32_config(struct vo *vo)
{
@@ -1704,26 +1927,15 @@ static void w32_api_load(struct vo_w32_state *w32)
// Available since Win10
w32->api.pAdjustWindowRectExForDpi = !user32_dll ? NULL :
(void *)GetProcAddress(user32_dll, "AdjustWindowRectExForDpi");
-
- // imm32.dll must be loaded dynamically
- // to account for machines without East Asian language support
- HMODULE imm32_dll = LoadLibraryW(L"imm32.dll");
- w32->api.pImmDisableIME = !imm32_dll ? NULL :
- (void *)GetProcAddress(imm32_dll, "ImmDisableIME");
+ w32->api.pGetSystemMetricsForDpi = !user32_dll ? NULL :
+ (void *)GetProcAddress(user32_dll, "GetSystemMetricsForDpi");
// Dark mode related functions, available since the 1809 Windows 10 update
// Check the Windows build version as on previous versions used ordinals
// may point to unexpected code/data. Alternatively could check uxtheme.dll
// version directly, but it is little bit more boilerplate code, and build
// number is good enough check.
- void (WINAPI *pRtlGetNtVersionNumbers)(LPDWORD, LPDWORD, LPDWORD) =
- (void *)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers");
-
- DWORD major, build;
- pRtlGetNtVersionNumbers(&major, NULL, &build);
- build &= ~0xF0000000;
-
- HMODULE uxtheme_dll = (major < 10 || build < 17763) ? NULL :
+ HMODULE uxtheme_dll = !check_windows10_build(17763) ? NULL :
GetModuleHandle(L"uxtheme.dll");
w32->api.pShouldAppsUseDarkMode = !uxtheme_dll ? NULL :
(void *)GetProcAddress(uxtheme_dll, MAKEINTRESOURCEA(132));
@@ -1741,10 +1953,6 @@ static MP_THREAD_VOID gui_thread(void *ptr)
w32_api_load(w32);
- // Disables the IME for windows on this thread
- if (w32->api.pImmDisableIME)
- w32->api.pImmDisableIME(0);
-
if (w32->opts->WinID >= 0)
w32->parent = (HWND)(intptr_t)(w32->opts->WinID);
@@ -1776,6 +1984,8 @@ static MP_THREAD_VOID gui_thread(void *ptr)
update_affinity(w32);
if (w32->opts->backdrop_type)
update_backdrop(w32);
+ if (w32->opts->cursor_passthrough)
+ update_cursor_passthrough(w32);
if (SUCCEEDED(OleInitialize(NULL))) {
ole_ok = true;
@@ -1805,7 +2015,7 @@ static MP_THREAD_VOID gui_thread(void *ptr)
ITaskbarList3_Release(w32->taskbar_list3);
w32->taskbar_list3 = NULL;
} else {
- w32->tbtnCreatedMsg = RegisterWindowMessage(L"TaskbarButtonCreated");
+ w32->tbtn_created_msg = RegisterWindowMessage(L"TaskbarButtonCreated");
}
}
} else {
@@ -1813,7 +2023,7 @@ static MP_THREAD_VOID gui_thread(void *ptr)
}
w32->tracking = FALSE;
- w32->trackEvent = (TRACKMOUSEEVENT){
+ w32->track_event = (TRACKMOUSEEVENT){
.cbSize = sizeof(TRACKMOUSEEVENT),
.dwFlags = TME_LEAVE,
.hwndTrack = w32->window,
@@ -1866,6 +2076,7 @@ bool vo_w32_init(struct vo *vo)
.dispatch = mp_dispatch_create(w32),
};
w32->opts = w32->opts_cache->opts;
+ w32->menu_ctx = mp_win32_menu_init();
vo->w32 = w32;
if (mp_thread_create(&w32->thread, gui_thread, w32))
@@ -1949,6 +2160,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
struct mp_vo_opts *vo_opts = w32->opts_cache->opts;
if (changed_option == &vo_opts->fullscreen) {
+ if (!vo_opts->fullscreen)
+ update_maximized_state(w32, true);
reinit_window_state(w32);
} else if (changed_option == &vo_opts->window_affinity) {
update_affinity(w32);
@@ -1956,6 +2169,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
update_window_state(w32);
} else if (changed_option == &vo_opts->backdrop_type) {
update_backdrop(w32);
+ } else if (changed_option == &vo_opts->cursor_passthrough) {
+ update_cursor_passthrough(w32);
} else if (changed_option == &vo_opts->border ||
changed_option == &vo_opts->title_bar)
{
@@ -1964,9 +2179,16 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
} else if (changed_option == &vo_opts->window_minimized) {
update_minimized_state(w32);
} else if (changed_option == &vo_opts->window_maximized) {
- update_maximized_state(w32);
+ update_maximized_state(w32, false);
} else if (changed_option == &vo_opts->window_corners) {
update_corners_pref(w32);
+ } else if (changed_option == &vo_opts->geometry || changed_option == &vo_opts->autofit ||
+ changed_option == &vo_opts->autofit_smaller || changed_option == &vo_opts->autofit_larger)
+ {
+ if (w32->opts->window_maximized) {
+ w32->unmaximize = true;
+ }
+ window_reconfig(w32, true);
}
}
@@ -1989,8 +2211,8 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
return VO_FALSE;
RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc;
- s[0] = rect_w(*rc) / w32->dpi_scale;
- s[1] = rect_h(*rc) / w32->dpi_scale;
+ s[0] = rect_w(*rc);
+ s[1] = rect_h(*rc);
return VO_TRUE;
}
case VOCTRL_SET_UNFS_WINDOW_SIZE: {
@@ -1999,12 +2221,12 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
if (!w32->window_bounds_initialized)
return VO_FALSE;
- s[0] *= w32->dpi_scale;
- s[1] *= w32->dpi_scale;
-
RECT *rc = w32->current_fs ? &w32->prev_windowrc : &w32->windowrc;
resize_and_move_rect(w32, rc, s[0], s[1]);
+ if (w32->opts->window_maximized) {
+ w32->unmaximize = true;
+ }
w32->fit_on_screen = true;
reinit_window_state(w32);
return VO_TRUE;
@@ -2064,6 +2286,15 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
case VOCTRL_GET_FOCUSED:
*(bool *)arg = w32->focused;
return VO_TRUE;
+ case VOCTRL_BEGIN_DRAGGING:
+ w32->start_dragging = true;
+ return VO_TRUE;
+ case VOCTRL_SHOW_MENU:
+ PostMessageW(w32->window, WM_SHOWMENU, 0, 0);
+ return VO_TRUE;
+ case VOCTRL_UPDATE_MENU:
+ mp_win32_menu_update(w32->menu_ctx, (struct mpv_node *)arg);
+ return VO_TRUE;
}
return VO_NOTIMPL;
}
@@ -2127,6 +2358,7 @@ void vo_w32_uninit(struct vo *vo)
AvRevertMmThreadCharacteristics(w32->avrt_handle);
+ mp_win32_menu_uninit(w32->menu_ctx);
talloc_free(w32);
vo->w32 = NULL;
}
@@ -2142,3 +2374,24 @@ void vo_w32_run_on_thread(struct vo *vo, void (*cb)(void *ctx), void *ctx)
struct vo_w32_state *w32 = vo->w32;
mp_dispatch_run(w32->dispatch, cb, ctx);
}
+
+void vo_w32_set_transparency(struct vo *vo, bool enable)
+{
+ struct vo_w32_state *w32 = vo->w32;
+ if (w32->parent)
+ return;
+
+ DWM_BLURBEHIND dbb = {0};
+ if (enable) {
+ HRGN rgn = CreateRectRgn(0, 0, -1, -1);
+ dbb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
+ dbb.hRgnBlur = rgn;
+ dbb.fEnable = TRUE;
+ DwmEnableBlurBehindWindow(w32->window, &dbb);
+ DeleteObject(rgn);
+ } else {
+ dbb.dwFlags = DWM_BB_ENABLE;
+ dbb.fEnable = FALSE;
+ DwmEnableBlurBehindWindow(w32->window, &dbb);
+ }
+}