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.c838
1 files changed, 677 insertions, 161 deletions
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index f228bc99b6..f7af24fcc3 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -15,10 +15,11 @@
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stdio.h>
-#include <limits.h>
-#include <pthread.h>
#include <assert.h>
+#include <limits.h>
+#include <stdatomic.h>
+#include <stdio.h>
+
#include <windows.h>
#include <windowsx.h>
#include <dwmapi.h>
@@ -39,10 +40,10 @@
#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"
-#include "osdep/atomic.h"
#include "misc/dispatch.h"
#include "misc/rendezvous.h"
#include "mpv_talloc.h"
@@ -54,6 +55,22 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define WM_DPICHANGED (0x02E0)
#endif
+#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
+#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
+#endif
+
+#ifndef DWMWA_VISIBLE_FRAME_BORDER_THICKNESS
+#define DWMWA_VISIBLE_FRAME_BORDER_THICKNESS 37
+#endif
+
+#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 {
MDT_EFFECTIVE_DPI = 0,
@@ -66,10 +83,14 @@ 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);
};
struct vo_w32_state {
@@ -79,7 +100,7 @@ struct vo_w32_state {
struct m_config_cache *opts_cache;
struct input_ctx *input_ctx;
- pthread_t thread;
+ mp_thread thread;
bool terminate;
struct mp_dispatch_queue *dispatch; // used to run stuff on the GUI thread
bool in_dispatch;
@@ -91,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
@@ -121,7 +144,7 @@ struct vo_w32_state {
atomic_uint event_flags;
BOOL tracking;
- TRACKMOUSEEVENT trackEvent;
+ TRACKMOUSEEVENT track_event;
int mouse_x;
int mouse_y;
@@ -141,10 +164,12 @@ struct vo_w32_state {
// entire virtual desktop area - but we still limit to one monitor size.
bool fit_on_screen;
+ bool win_force_pos;
+
ITaskbarList2 *taskbar_list;
ITaskbarList3 *taskbar_list3;
- UINT tbtnCreatedMsg;
- bool tbtnCreated;
+ UINT tbtn_created_msg;
+ bool tbtn_created;
struct voctrl_playback_state current_pstate;
@@ -152,15 +177,42 @@ struct vo_w32_state {
double display_fps;
bool moving;
- bool snapped;
+
+ union {
+ uint8_t snapped;
+ struct {
+ uint8_t snapped_left : 1;
+ uint8_t snapped_right : 1;
+ uint8_t snapped_top : 1;
+ uint8_t snapped_bottom : 1;
+ };
+ };
int snap_dx;
int snap_dy;
HANDLE avrt_handle;
+
+ bool cleared;
+ bool dragging;
+ bool start_dragging;
+ BOOL win_arranging;
+
+ bool conversion_mode_init;
+ bool unmaximize;
};
-static void add_window_borders(struct vo_w32_state *w32, HWND hwnd, RECT *rc)
+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)
+ return;
+
if (w32->api.pAdjustWindowRectExForDpi) {
w32->api.pAdjustWindowRectExForDpi(rc,
GetWindowLongPtrW(hwnd, GWL_STYLE), 0,
@@ -170,6 +222,69 @@ static void add_window_borders(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
+ // 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)
static void subtract_window_borders(struct vo_w32_state *w32, HWND hwnd, RECT *rc)
{
@@ -186,51 +301,58 @@ static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y)
if (IsMaximized(w32->window))
return HTCLIENT;
- POINT mouse = { x, y };
- ScreenToClient(w32->window, &mouse);
+ RECT rc;
+ if (!GetWindowRect(w32->window, &rc))
+ return HTNOWHERE;
- // The horizontal frame should be the same size as the vertical frame,
- // since the NONCLIENTMETRICS structure does not distinguish between them
- int frame_size = GetSystemMetrics(SM_CXFRAME) +
- GetSystemMetrics(SM_CXPADDEDBORDER);
- // The diagonal size handles are slightly wider than the side borders
- int diagonal_width = frame_size * 2 + GetSystemMetrics(SM_CXBORDER);
+ POINT frame = {get_system_metrics(w32, SM_CXSIZEFRAME),
+ get_system_metrics(w32, SM_CYSIZEFRAME)};
+ if (w32->opts->border) {
+ frame.x += get_system_metrics(w32, SM_CXPADDEDBORDER);
+ frame.y += get_system_metrics(w32, SM_CXPADDEDBORDER);
+ if (!w32->opts->title_bar)
+ rc.top -= get_system_metrics(w32, SM_CXPADDEDBORDER);
+ }
+ InflateRect(&rc, -frame.x, -frame.y);
// Hit-test top border
- if (mouse.y < frame_size) {
- if (mouse.x < diagonal_width)
+ if (y < rc.top) {
+ if (x < rc.left)
return HTTOPLEFT;
- if (mouse.x >= rect_w(w32->windowrc) - diagonal_width)
+ if (x > rc.right)
return HTTOPRIGHT;
return HTTOP;
}
// Hit-test bottom border
- if (mouse.y >= rect_h(w32->windowrc) - frame_size) {
- if (mouse.x < diagonal_width)
+ if (y > rc.bottom) {
+ if (x < rc.left)
return HTBOTTOMLEFT;
- if (mouse.x >= rect_w(w32->windowrc) - diagonal_width)
+ if (x > rc.right)
return HTBOTTOMRIGHT;
return HTBOTTOM;
}
// Hit-test side borders
- if (mouse.x < frame_size)
+ if (x < rc.left)
return HTLEFT;
- if (mouse.x >= rect_w(w32->windowrc) - frame_size)
+ if (x > rc.right)
return HTRIGHT;
return HTCLIENT;
}
// turn a WMSZ_* input value in v into the border that should be resized
+// take into consideration which borders are snapped to avoid detaching
// returns: 0=left, 1=top, 2=right, 3=bottom, -1=undefined
-static int get_resize_border(int v)
+static int get_resize_border(struct vo_w32_state *w32, int v)
{
switch (v) {
- case WMSZ_LEFT: return 3;
- case WMSZ_TOP: return 2;
- case WMSZ_RIGHT: return 3;
- case WMSZ_BOTTOM: return 2;
+ case WMSZ_LEFT:
+ case WMSZ_RIGHT:
+ return w32->snapped_bottom ? 1 : 3;
+ case WMSZ_TOP:
+ case WMSZ_BOTTOM:
+ return w32->snapped_right ? 0 : 2;
case WMSZ_TOPLEFT: return 1;
case WMSZ_TOPRIGHT: return 1;
case WMSZ_BOTTOMLEFT: return 3;
@@ -301,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);
@@ -313,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
@@ -385,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));
@@ -396,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)
@@ -413,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;
@@ -426,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;
}
@@ -510,6 +639,10 @@ static double get_refresh_rate_from_gdi(const wchar_t *device)
case 95:
case 119:
case 143:
+ case 164:
+ case 239:
+ case 359:
+ case 479:
rv = (rv + 1) / 1.001;
}
@@ -519,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;
}
@@ -556,7 +691,8 @@ 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);
}
static void update_display_info(struct vo_w32_state *w32)
@@ -612,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) {
@@ -659,8 +795,15 @@ static HMONITOR get_default_monitor(struct vo_w32_state *w32)
w32->opts->screen_id;
// Handle --fs-screen=<all|default> and --screen=default
- if (id < 0)
- return MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY);
+ if (id < 0) {
+ if (w32->win_force_pos && !w32->current_fs) {
+ // Get window from forced position
+ return MonitorFromRect(&w32->windowrc, MONITOR_DEFAULTTOPRIMARY);
+ } else {
+ // Let compositor decide
+ return MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY);
+ }
+ }
HMONITOR mon = get_monitor(id);
if (mon)
@@ -688,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;
}
@@ -702,13 +845,27 @@ static RECT get_working_area(struct vo_w32_state *w32)
get_monitor_info(w32).rcWork;
}
+// Adjust working area boundaries to compensate for invisible borders.
+static void adjust_working_area_for_extended_frame(RECT *wa_rect, RECT *wnd_rect, HWND wnd)
+{
+ RECT frame = {0};
+
+ if (DwmGetWindowAttribute(wnd, DWMWA_EXTENDED_FRAME_BOUNDS,
+ &frame, sizeof(RECT)) == S_OK) {
+ wa_rect->left -= frame.left - wnd_rect->left;
+ wa_rect->top -= frame.top - wnd_rect->top;
+ wa_rect->right += wnd_rect->right - frame.right;
+ wa_rect->bottom += wnd_rect->bottom - frame.bottom;
+ }
+}
+
static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
{
if (w32->parent || w32->current_fs || IsMaximized(w32->window))
return false;
if (!w32->opts->snap_window) {
- w32->snapped = false;
+ w32->snapped = 0;
return false;
}
@@ -732,15 +889,7 @@ static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
// Get the work area to let the window snap to taskbar
wr = get_working_area(w32);
- // Check for invisible borders and adjust the work area size
- RECT frame = {0};
- if (DwmGetWindowAttribute(w32->window, DWMWA_EXTENDED_FRAME_BOUNDS,
- &frame, sizeof(RECT)) == S_OK) {
- wr.left -= frame.left - rect.left;
- wr.top -= frame.top - rect.top;
- wr.right += rect.right - frame.right;
- wr.bottom += rect.bottom - frame.bottom;
- }
+ adjust_working_area_for_extended_frame(&wr, &rect, w32->window);
// Let the window to unsnap by changing its position,
// otherwise it will stick to the screen edges forever
@@ -751,48 +900,68 @@ static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc)
}
int threshold = (w32->dpi * 16) / 96;
- bool snapped = false;
+ bool was_snapped = !!w32->snapped;
+ w32->snapped = 0;
// Adjust X position
+ // snapped_left & snapped_right are mutually exclusive
if (abs(rect.left - wr.left) < threshold) {
- snapped = true;
+ w32->snapped_left = 1;
OffsetRect(&rect, wr.left - rect.left, 0);
} else if (abs(rect.right - wr.right) < threshold) {
- snapped = true;
+ w32->snapped_right = 1;
OffsetRect(&rect, wr.right - rect.right, 0);
}
// Adjust Y position
+ // snapped_top & snapped_bottom are mutually exclusive
if (abs(rect.top - wr.top) < threshold) {
- snapped = true;
+ w32->snapped_top = 1;
OffsetRect(&rect, 0, wr.top - rect.top);
} else if (abs(rect.bottom - wr.bottom) < threshold) {
- snapped = true;
+ w32->snapped_bottom = 1;
OffsetRect(&rect, 0, wr.bottom - rect.bottom);
}
- if (!w32->snapped && snapped) {
+ if (!was_snapped && w32->snapped != 0) {
w32->snap_dx = cursor.x - rc->left;
w32->snap_dy = cursor.y - rc->top;
}
- w32->snapped = snapped;
*rc = rect;
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;
+ const DWORD NO_FRAME = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_THICKFRAME;
const DWORD FRAME = WS_OVERLAPPEDWINDOW;
- const DWORD FULLSCREEN = NO_FRAME | WS_SYSMENU;
+ const DWORD FULLSCREEN = NO_FRAME & ~WS_THICKFRAME;
style &= ~(NO_FRAME | FRAME | FULLSCREEN);
+ style |= WS_SYSMENU;
if (w32->current_fs) {
style |= FULLSCREEN;
} else {
style |= w32->opts->border ? FRAME : NO_FRAME;
+ if (!w32->opts->title_bar && is_high_contrast())
+ style &= ~WS_CAPTION;
}
return style;
}
+static DWORD update_exstyle(struct vo_w32_state *w32, DWORD exstyle)
+{
+ exstyle &= ~(WS_EX_TOOLWINDOW);
+ if (!w32->opts->show_in_taskbar)
+ exstyle |= WS_EX_TOOLWINDOW;
+ return exstyle;
+}
+
static void update_window_style(struct vo_w32_state *w32)
{
if (w32->parent)
@@ -802,13 +971,38 @@ static void update_window_style(struct vo_w32_state *w32)
// has to be saved now and restored after setting the new style.
const RECT wr = w32->windowrc;
const DWORD style = GetWindowLongPtrW(w32->window, GWL_STYLE);
+ const DWORD exstyle = GetWindowLongPtrW(w32->window, GWL_EXSTYLE);
SetWindowLongPtrW(w32->window, GWL_STYLE, update_style(w32, style));
+ SetWindowLongPtrW(w32->window, GWL_EXSTYLE, update_exstyle(w32, exstyle));
w32->windowrc = wr;
}
+// Resize window rect to width = w and height = h. If window is snapped,
+// don't let it detach from snapped borders. Otherwise resize around the center.
+static void resize_and_move_rect(struct vo_w32_state *w32, RECT *rc, int w, int h)
+{
+ int x, y;
+
+ if (w32->snapped_left)
+ x = rc->left;
+ else if (w32->snapped_right)
+ x = rc->right - w;
+ else
+ x = rc->left + rect_w(*rc) / 2 - w / 2;
+
+ if (w32->snapped_top)
+ y = rc->top;
+ else if (w32->snapped_bottom)
+ y = rc->bottom - h;
+ else
+ y = rc->top + rect_h(*rc) / 2 - h / 2;
+
+ SetRect(rc, x, y, x + w, y + h);
+}
+
// If rc is wider/taller than n_w/n_h, shrink rc size while keeping the center.
// returns true if the rectangle was modified.
-static bool fit_rect_size(RECT *rc, long n_w, long n_h)
+static bool fit_rect_size(struct vo_w32_state *w32, RECT *rc, long n_w, long n_h)
{
// nothing to do if we already fit.
int o_w = rect_w(*rc), o_h = rect_h(*rc);
@@ -824,10 +1018,8 @@ static bool fit_rect_size(RECT *rc, long n_w, long n_h)
n_w = n_h * o_asp;
}
- // Calculate new position and save the rect
- const int x = rc->left + o_w / 2 - n_w / 2;
- const int y = rc->top + o_h / 2 - n_h / 2;
- SetRect(rc, x, y, x + n_w, y + n_h);
+ resize_and_move_rect(w32, rc, n_w, n_h);
+
return true;
}
@@ -839,7 +1031,11 @@ static void fit_window_on_screen(struct vo_w32_state *w32)
if (w32->opts->border)
subtract_window_borders(w32, w32->window, &screen);
- bool adjusted = fit_rect_size(&w32->windowrc, rect_w(screen), rect_h(screen));
+ RECT window_rect;
+ if (GetWindowRect(w32->window, &window_rect))
+ adjust_working_area_for_extended_frame(&screen, &window_rect, w32->window);
+
+ bool adjusted = fit_rect_size(w32, &w32->windowrc, rect_w(screen), rect_h(screen));
if (w32->windowrc.top < screen.top) {
// if the top-edge of client area is above the target area (mainly
@@ -910,14 +1106,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 };
@@ -947,6 +1142,16 @@ static bool is_visible(HWND window)
return GetWindowLongPtrW(window, GWL_STYLE) & WS_VISIBLE;
}
+//Set the mpv window's affinity.
+//This will affect how it's displayed on the desktop and in system-level operations like taking screenshots.
+static void update_affinity(struct vo_w32_state *w32)
+{
+ if (!w32 || w32->parent) {
+ return;
+ }
+ SetWindowDisplayAffinity(w32->window, w32->opts->window_affinity);
+}
+
static void update_window_state(struct vo_w32_state *w32)
{
if (w32->parent)
@@ -959,11 +1164,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_SHOWMINIMIZED);
- update_maximized_state(w32); // Set the WPF_RESTORETOMAXIMIZED flag
+ ShowWindow(w32->window, SW_SHOWMINNOACTIVE);
+ update_maximized_state(w32, false); // Set the WPF_RESTORETOMAXIMIZED flag
} else if (w32->opts->window_maximized) {
ShowWindow(w32->window, SW_SHOWMAXIMIZED);
} else {
@@ -978,9 +1196,52 @@ static void update_window_state(struct vo_w32_state *w32)
w32->window, w32->current_fs);
}
+ // Update snapping status if needed
+ if (w32->opts->snap_window && !w32->parent &&
+ !w32->current_fs && !IsMaximized(w32->window)) {
+ RECT wa = get_working_area(w32);
+
+ adjust_working_area_for_extended_frame(&wa, &wr, w32->window);
+
+ // snapped_left & snapped_right are mutually exclusive
+ if (wa.left == wr.left && wa.right == wr.right) {
+ // Leave as is.
+ } else if (wa.left == wr.left) {
+ w32->snapped_left = 1;
+ w32->snapped_right = 0;
+ } else if (wa.right == wr.right) {
+ w32->snapped_right = 1;
+ w32->snapped_left = 0;
+ } else {
+ w32->snapped_left = w32->snapped_right = 0;
+ }
+
+ // snapped_top & snapped_bottom are mutually exclusive
+ if (wa.top == wr.top && wa.bottom == wr.bottom) {
+ // Leave as is.
+ } else if (wa.top == wr.top) {
+ w32->snapped_top = 1;
+ w32->snapped_bottom = 0;
+ } else if (wa.bottom == wr.bottom) {
+ w32->snapped_bottom = 1;
+ w32->snapped_top = 0;
+ } else {
+ w32->snapped_top = w32->snapped_bottom = 0;
+ }
+ }
+
signal_events(w32, VO_EVENT_RESIZE);
}
+static void update_corners_pref(const struct vo_w32_state *w32) {
+ if (w32->parent)
+ return;
+
+ int pref = w32->current_fs ? 0 : w32->opts->window_corners;
+ DwmSetWindowAttribute(w32->window, DWMWA_WINDOW_CORNER_PREFERENCE,
+ &pref, sizeof(pref));
+}
+
static void reinit_window_state(struct vo_w32_state *w32)
{
if (w32->parent)
@@ -988,6 +1249,7 @@ static void reinit_window_state(struct vo_w32_state *w32)
// The order matters: fs state should be updated prior to changing styles
update_fullscreen_state(w32);
+ update_corners_pref(w32);
update_window_style(w32);
// fit_on_screen is applied at most once when/if applicable (normal win).
@@ -1000,6 +1262,73 @@ static void reinit_window_state(struct vo_w32_state *w32)
update_window_state(w32);
}
+// Follow Windows settings and update dark mode state
+// Microsoft documented how to enable dark mode for title bar:
+// https://learn.microsoft.com/windows/apps/desktop/modernize/apply-windows-themes
+// https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
+// Documentation says to set the DWMWA_USE_IMMERSIVE_DARK_MODE attribute to
+// TRUE to honor dark mode for the window, FALSE to always use light mode. While
+// in fact setting it to TRUE causes dark mode to be always enabled, regardless
+// of the settings. Since it is quite unlikely that it will be fixed, just use
+// UxTheme API to check if dark mode should be applied and while at it enable it
+// fully. Ideally this function should only call the DwmSetWindowAttribute(),
+// but it just doesn't work as documented.
+static void update_dark_mode(const struct vo_w32_state *w32)
+{
+ if (w32->api.pSetPreferredAppMode)
+ w32->api.pSetPreferredAppMode(1); // allow dark mode
+
+ // if pShouldAppsUseDarkMode is not available, just assume it to be true
+ 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);
+
+ DwmSetWindowAttribute(w32->window, DWMWA_USE_IMMERSIVE_DARK_MODE,
+ &use_dark_mode, sizeof(use_dark_mode));
+}
+
+static void update_backdrop(const struct vo_w32_state *w32)
+{
+ if (w32->parent)
+ return;
+
+ int backdropType = w32->opts->backdrop_type;
+ DwmSetWindowAttribute(w32->window, DWMWA_SYSTEMBACKDROP_TYPE,
+ &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)
{
@@ -1026,14 +1355,24 @@ 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: // no need to erase background separately
- return 1;
+ case WM_ERASEBKGND:
+ if (w32->cleared || !w32->opts->border || w32->current_fs)
+ return TRUE;
+ break;
case WM_PAINT:
+ w32->cleared = true;
signal_events(w32, VO_EVENT_EXPOSE);
break;
case WM_MOVE: {
+ w32->moving = false;
const int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
OffsetRect(&w32->windowrc, x - w32->windowrc.left,
y - w32->windowrc.top);
@@ -1042,19 +1381,30 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
signal_events(w32, VO_EVENT_WIN_STATE);
update_display_info(w32); // if we moved between monitors
- MP_DBG(w32, "move window: %d:%d\n", x, y);
break;
}
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;
}
case WM_ENTERSIZEMOVE:
w32->moving = true;
- if (w32->snapped) {
+ if (w32->snapped != 0) {
// Save the cursor offset from the window borders,
// so the player window can be unsnapped later
RECT rc;
@@ -1069,9 +1419,6 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
w32->moving = false;
break;
case WM_SIZE: {
- if (w32->moving)
- w32->snapped = false;
-
const int w = LOWORD(lParam), h = HIWORD(lParam);
if (w > 0 && h > 0) {
w32->windowrc.right = w32->windowrc.left + w;
@@ -1122,7 +1469,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
int d_h = c_w / aspect - c_h;
int d_corners[4] = { d_w, d_h, -d_w, -d_h };
int corners[4] = { rc->left, rc->top, rc->right, rc->bottom };
- int corner = get_resize_border(wParam);
+ int corner = get_resize_border(w32, wParam);
if (corner >= 0)
corners[corner] -= d_corners[corner];
*rc = (RECT) { corners[0], corners[1], corners[2], corners[3] };
@@ -1154,6 +1501,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-&