diff options
Diffstat (limited to 'video/out/w32_common.c')
-rw-r--r-- | video/out/w32_common.c | 457 |
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); + } +} |