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:
pavelxdd 2016-12-09 21:22:33 +03:00 committed by wm4
parent cfda696580
commit 9c90c902c1
5 changed files with 153 additions and 0 deletions

View File

@ -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.

View File

@ -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,

View File

@ -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;

View File

@ -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" },

View File

@ -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;