mirror of https://github.com/mpv-player/mpv
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
This commit is contained in:
parent
cfda696580
commit
9c90c902c1
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <assert.h>
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
#include <dwmapi.h>
|
||||
#include <ole2.h>
|
||||
#include <shobjidl.h>
|
||||
#include <avrt.h>
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue