From 9c90c902c11f557577f22e72a3a2db0034c511d8 Mon Sep 17 00:00:00 2001 From: pavelxdd Date: Fri, 9 Dec 2016 21:22:33 +0300 Subject: win32: snap to screen edges Disabled by default. The snap sensitivity value depends on the screen DPI. The default value is 16px on a 96 DPI screen. Fixes #2248 --- DOCS/man/options.rst | 3 + options/options.c | 2 + options/options.h | 1 + player/command.c | 1 + video/out/w32_common.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index c1942cf0c6..a6034e512e 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -2112,6 +2112,9 @@ Window Enabled by default. +``--snap-window`` + (Windows only) Snap the player window to screen edges. + ``--ontop`` Makes the player window stay on top of other windows. diff --git a/options/options.c b/options/options.c index ed10299480..6547f06074 100644 --- a/options/options.c +++ b/options/options.c @@ -155,6 +155,7 @@ static const m_option_t mp_vo_opt_list[] = { OPT_SETTINGSLIST("vo", video_driver_list, 0, &vo_obj_list, ), OPT_SUBSTRUCT("sws", sws_opts, sws_conf, 0), OPT_FLAG("taskbar-progress", taskbar_progress, 0), + OPT_FLAG("snap-window", snap_window, 0), OPT_FLAG("ontop", ontop, 0), OPT_FLAG("border", border, 0), OPT_FLAG("fit-border", fit_border, 0), @@ -222,6 +223,7 @@ const struct m_sub_options vo_sub_opts = { .keepaspect_window = 1, .hidpi_window_scale = 1, .taskbar_progress = 1, + .snap_window = 0, .border = 1, .fit_border = 1, .WinID = -1, diff --git a/options/options.h b/options/options.h index 6199eae5fe..b0687ac9e5 100644 --- a/options/options.h +++ b/options/options.h @@ -10,6 +10,7 @@ typedef struct mp_vo_opts { struct m_obj_settings *video_driver_list; int taskbar_progress; + int snap_window; int ontop; int fullscreen; int border; diff --git a/player/command.c b/player/command.c index da5d985c17..7023111556 100644 --- a/player/command.c +++ b/player/command.c @@ -4270,6 +4270,7 @@ static const struct property_osd_display { // video { "panscan", "Panscan", .osd_progbar = OSD_PANSCAN }, { "taskbar-progress", "Progress in taskbar" }, + { "snap-window", "Snap to screen edges" }, { "ontop", "Stay on top" }, { "border", "Border" }, { "framedrop", "Framedrop" }, diff --git a/video/out/w32_common.c b/video/out/w32_common.c index dad9b10799..4ff8d7b5bd 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -47,8 +48,25 @@ EXTERN_C IMAGE_DOS_HEADER __ImageBase; #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) +#ifndef WM_DPICHANGED +#define WM_DPICHANGED (0x02E0) +#endif + +#ifndef DPI_ENUMS_DECLARED +typedef enum MONITOR_DPI_TYPE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; +#endif + static __thread struct vo_w32_state *w32_thread_context; +struct w32_api { + HRESULT (WINAPI *pGetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); +}; + struct vo_w32_state { struct mp_log *log; struct vo *vo; @@ -59,6 +77,8 @@ struct vo_w32_state { bool terminate; struct mp_dispatch_queue *dispatch; // used to run stuff on the GUI thread + struct w32_api api; // stores functions from dynamically loaded DLLs + HWND window; HWND parent; // 0 normally, set in embedding mode HHOOK parent_win_hook; @@ -92,6 +112,8 @@ struct vo_w32_state { uint32_t o_dwidth; uint32_t o_dheight; + int dpi; + bool disable_screensaver; bool cursor_visible; atomic_uint event_flags; @@ -118,6 +140,10 @@ struct vo_w32_state { // updates on move/resize/displaychange double display_fps; + bool snapped; + int snap_dx; + int snap_dy; + HANDLE avrt_handle; }; @@ -630,6 +656,26 @@ done: return name; } +static void update_dpi(struct vo_w32_state *w32) +{ + UINT dpiX, dpiY; + if (w32->api.pGetDpiForMonitor && w32->api.pGetDpiForMonitor(w32->monitor, + MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) { + w32->dpi = (int)dpiX; + MP_VERBOSE(w32, "DPI detected from the new API: %d\n", w32->dpi); + return; + } + HDC hdc = GetDC(NULL); + if (hdc) { + w32->dpi = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(NULL, hdc); + MP_VERBOSE(w32, "DPI detected from the old API: %d\n", w32->dpi); + } else { + w32->dpi = 96; + MP_VERBOSE(w32, "Couldn't determine DPI, falling back to %d\n", w32->dpi); + } +} + static void update_display_info(struct vo_w32_state *w32) { HMONITOR monitor = MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY); @@ -637,6 +683,8 @@ static void update_display_info(struct vo_w32_state *w32) return; w32->monitor = monitor; + update_dpi(w32); + MONITORINFOEXW mi = { .cbSize = sizeof mi }; GetMonitorInfoW(monitor, (MONITORINFO*)&mi); @@ -697,6 +745,75 @@ static void update_playback_state(struct vo_w32_state *w32) TBPF_NORMAL); } +static bool snap_to_screen_edges(struct vo_w32_state *w32, RECT *rc) +{ + if (!w32->opts->snap_window) { + w32->snapped = false; + return false; + } + + RECT rect; + POINT cursor; + if (!GetWindowRect(w32->window, &rect) || !GetCursorPos(&cursor)) + return false; + // Check for aero snapping + if ((rc->right - rc->left != rect.right - rect.left) || + (rc->bottom - rc->top != rect.bottom - rect.top)) + return false; + + MONITORINFO mi = { .cbSize = sizeof(mi) }; + if (!GetMonitorInfoW(w32->monitor, &mi)) + return false; + // Get the work area to let the window snap to taskbar + RECT wr = mi.rcWork; + + // 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; + } + + // Let the window to unsnap by changing its position, + // otherwise it will stick to the screen edges forever + rect = *rc; + if (w32->snapped) { + OffsetRect(&rect, cursor.x - rect.left - w32->snap_dx, + cursor.y - rect.top - w32->snap_dy); + } + + int threshold = (w32->dpi * 16) / 96; + bool snapped = false; + // Adjust X position + if (abs(rect.left - wr.left) < threshold) { + snapped = true; + OffsetRect(&rect, wr.left - rect.left, 0); + } else if (abs(rect.right - wr.right) < threshold) { + snapped = true; + OffsetRect(&rect, wr.right - rect.right, 0); + } + // Adjust Y position + if (abs(rect.top - wr.top) < threshold) { + snapped = true; + OffsetRect(&rect, 0, wr.top - rect.top); + } else if (abs(rect.bottom - wr.bottom) < threshold) { + snapped = true; + OffsetRect(&rect, 0, wr.bottom - rect.bottom); + } + + if (!w32->snapped && snapped) { + w32->snap_dx = cursor.x - rc->left; + w32->snap_dy = cursor.y - rc->top; + } + + w32->snapped = snapped; + *rc = rect; + return true; +} + static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -730,6 +847,24 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, MP_DBG(w32, "move window: %d:%d\n", w32->window_x, w32->window_y); break; } + case WM_MOVING: { + RECT *rc = (RECT*)lParam; + if (snap_to_screen_edges(w32, rc)) + return TRUE; + break; + } + case WM_ENTERSIZEMOVE: + if (w32->snapped) { + // Save the cursor offset from the window borders, + // so the player window can be unsnapped later + RECT rc; + POINT cursor; + if (GetWindowRect(w32->window, &rc) && GetCursorPos(&cursor)) { + w32->snap_dx = cursor.x - rc.left; + w32->snap_dy = cursor.y - rc.top; + } + } + break; case WM_SIZE: { RECT r; if (GetClientRect(w32->window, &r) && r.right > 0 && r.bottom > 0) { @@ -767,6 +902,9 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, return TRUE; } break; + case WM_DPICHANGED: + update_display_info(w32); + break; case WM_CLOSE: // Don't actually allow it to destroy the window, or whatever else it // is that will make us lose WM_USER wakeups. @@ -1333,6 +1471,13 @@ static void thread_disable_ime(void) FreeLibrary(imm32); } +static void w32_api_load(struct vo_w32_state *w32) +{ + HMODULE shcore_dll = LoadLibraryW(L"shcore.dll"); + w32->api.pGetDpiForMonitor = !shcore_dll ? NULL : + (void *)GetProcAddress(shcore_dll, "GetDpiForMonitor"); +} + static void *gui_thread(void *ptr) { struct vo_w32_state *w32 = ptr; @@ -1341,6 +1486,7 @@ static void *gui_thread(void *ptr) mpthread_set_name("win32 window"); + w32_api_load(w32); thread_disable_ime(); w32_thread_context = w32; -- cgit v1.2.3