diff options
-rw-r--r-- | video/out/w32_common.c | 56 | ||||
-rw-r--r-- | video/out/win32/displayconfig.c | 236 | ||||
-rw-r--r-- | video/out/win32/displayconfig.h | 27 | ||||
-rw-r--r-- | waftools/detections/compiler.py | 2 | ||||
-rw-r--r-- | wscript_build.py | 1 |
5 files changed, 303 insertions, 19 deletions
diff --git a/video/out/w32_common.c b/video/out/w32_common.c index f76384b451..09088df79d 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -34,6 +34,7 @@ #include "vo.h" #include "win_state.h" #include "w32_common.h" +#include "win32/displayconfig.h" #include "osdep/io.h" #include "osdep/threads.h" #include "osdep/w32_keyboard.h" @@ -59,8 +60,8 @@ struct vo_w32_state { HWND window; HWND parent; // 0 normally, set in embedding mode - // Size and virtual position of the current screen. - struct mp_rect screenrc; + HMONITOR monitor; // Handle of the current screen + struct mp_rect screenrc; // Size and virtual position of the current screen // last non-fullscreen extends (updated only on fullscreen or on initialization) int prev_width; @@ -567,22 +568,17 @@ static void wakeup_gui_thread(void *ctx) PostMessage(w32->window, WM_USER, 0, 0); } -static double vo_w32_get_display_fps(struct vo_w32_state *w32) +static double get_refresh_rate_from_gdi(const wchar_t *device) { - // Get the device name of the monitor containing the window - HMONITOR mon = MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY); - MONITORINFOEXW mi = { .cbSize = sizeof mi }; - GetMonitorInfoW(mon, (MONITORINFO*)&mi); - - DEVMODE dm = { .dmSize = sizeof dm }; - if (!EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm)) - return -1; + DEVMODEW dm = { .dmSize = sizeof dm }; + if (!EnumDisplaySettingsW(device, ENUM_CURRENT_SETTINGS, &dm)) + return 0.0; // May return 0 or 1 which "represent the display hardware's default refresh rate" // https://msdn.microsoft.com/en-us/library/windows/desktop/dd183565%28v=vs.85%29.aspx // mpv validates this value with a threshold of 1, so don't return exactly 1 if (dm.dmDisplayFrequency == 1) - return 0; + return 0.0; // dm.dmDisplayFrequency is an integer which is rounded down, so it's // highly likely that 23 represents 24/1.001, 59 represents 60/1.001, etc. @@ -606,14 +602,37 @@ static double vo_w32_get_display_fps(struct vo_w32_state *w32) static void update_display_fps(struct vo_w32_state *w32) { - double fps = vo_w32_get_display_fps(w32); - if (fps != w32->display_fps) { - w32->display_fps = fps; + HMONITOR monitor = MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY); + if (w32->monitor == monitor) + return; + w32->monitor = monitor; + + MONITORINFOEXW mi = { .cbSize = sizeof mi }; + GetMonitorInfoW(monitor, (MONITORINFO*)&mi); + + // Try to get the monitor refresh rate. + double freq = 0.0; + + if (freq == 0.0) + freq = mp_w32_displayconfig_get_refresh_rate(mi.szDevice); + if (freq == 0.0) + freq = get_refresh_rate_from_gdi(mi.szDevice); + + if (freq != w32->display_fps) { + MP_VERBOSE(w32, "display-fps: %f\n", freq); + if (freq == 0.0) + MP_WARN(w32, "Couldn't determine monitor refresh rate\n"); + w32->display_fps = freq; signal_events(w32, VO_EVENT_WIN_STATE); - MP_VERBOSE(w32, "display-fps: %f\n", fps); } } +static void force_update_display_fps(struct vo_w32_state *w32) +{ + w32->monitor = 0; + update_display_fps(w32); +} + static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -648,13 +667,14 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, if (GetClientRect(w32->window, &r) && r.right > 0 && r.bottom > 0) { w32->dw = r.right; w32->dh = r.bottom; - update_display_fps(w32); // if we moved between monitors signal_events(w32, VO_EVENT_RESIZE); MP_VERBOSE(w32, "resize window: %d:%d\n", w32->dw, w32->dh); } // Window may have been minimized or restored signal_events(w32, VO_EVENT_WIN_STATE); + + update_display_fps(w32); break; } case WM_SIZING: @@ -799,7 +819,7 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, mouse_button |= MP_KEY_STATE_UP; break; case WM_DISPLAYCHANGE: - update_display_fps(w32); + force_update_display_fps(w32); break; } diff --git a/video/out/win32/displayconfig.c b/video/out/win32/displayconfig.c new file mode 100644 index 0000000000..635e8b6b36 --- /dev/null +++ b/video/out/win32/displayconfig.c @@ -0,0 +1,236 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <windows.h> +#include <stdbool.h> +#include <string.h> +#include <pthread.h> + +#include "displayconfig.h" + +#include "talloc.h" + +// Some DisplayConfig definitions are broken in mingw-w64 (as of 2015-3-13.) To +// get the correct struct alignment, it's necessary to define them properly. +#include <pshpack1.h> + +typedef enum { + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = -1, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 = 0, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SVIDEO = 1, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPOSITE_VIDEO = 2, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPONENT_VIDEO = 3, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DVI = 4, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI = 5, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_LVDS = 6, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_D_JPN = 8, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDI = 9, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL = 10, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED = 11, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EXTERNAL = 12, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED = 13, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDTVDONGLE = 14, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_MIRACAST = 15, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL = -2147483647 - 1, + MP_DISPLAYCONFIG_OUTPUT_TECHNOLOGY_FORCE_UINT32 = 0x7FFFFFFF +} MP_DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY; + +typedef struct MP_DISPLAYCONFIG_PATH_TARGET_INFO { + LUID adapterId; + UINT32 id; + UINT32 modeInfoIdx; + MP_DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology; + DISPLAYCONFIG_ROTATION rotation; + DISPLAYCONFIG_SCALING scaling; + DISPLAYCONFIG_RATIONAL refreshRate; + DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering; + WINBOOL targetAvailable; + UINT32 statusFlags; +} MP_DISPLAYCONFIG_PATH_TARGET_INFO; +#define DISPLAYCONFIG_PATH_TARGET_INFO MP_DISPLAYCONFIG_PATH_TARGET_INFO + +typedef struct MP_DISPLAYCONFIG_PATH_INFO { + DISPLAYCONFIG_PATH_SOURCE_INFO sourceInfo; + MP_DISPLAYCONFIG_PATH_TARGET_INFO targetInfo; + UINT32 flags; +} MP_DISPLAYCONFIG_PATH_INFO; +#define DISPLAYCONFIG_PATH_INFO MP_DISPLAYCONFIG_PATH_INFO + +typedef struct MP_DISPLAYCONFIG_TARGET_DEVICE_NAME { + DISPLAYCONFIG_DEVICE_INFO_HEADER header; + DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags; + MP_DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology; + UINT16 edidManufactureId; + UINT16 edidProductCodeId; + UINT32 connectorInstance; + WCHAR monitorFriendlyDeviceName[64]; + WCHAR monitorDevicePath[128]; +} MP_DISPLAYCONFIG_TARGET_DEVICE_NAME; +#define DISPLAYCONFIG_TARGET_DEVICE_NAME MP_DISPLAYCONFIG_TARGET_DEVICE_NAME + +#include <poppack.h> + +static pthread_once_t displayconfig_load_ran = PTHREAD_ONCE_INIT; +static bool displayconfig_loaded = false; + +static LONG (WINAPI *pDisplayConfigGetDeviceInfo)( + DISPLAYCONFIG_DEVICE_INFO_HEADER*); +static LONG (WINAPI *pGetDisplayConfigBufferSizes)(UINT32, UINT32*, UINT32*); +static LONG (WINAPI *pQueryDisplayConfig)(UINT32, UINT32*, + DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, + DISPLAYCONFIG_TOPOLOGY_ID*); + +static void displayconfig_load(void) +{ + HMODULE user32 = GetModuleHandleW(L"user32.dll"); + if (!user32) + return; + + pDisplayConfigGetDeviceInfo = + (LONG (WINAPI*)(DISPLAYCONFIG_DEVICE_INFO_HEADER*)) + GetProcAddress(user32, "DisplayConfigGetDeviceInfo"); + if (!pDisplayConfigGetDeviceInfo) + return; + pGetDisplayConfigBufferSizes = + (LONG (WINAPI*)(UINT32, UINT32*, UINT32*)) + GetProcAddress(user32, "GetDisplayConfigBufferSizes"); + if (!pGetDisplayConfigBufferSizes) + return; + pQueryDisplayConfig = + (LONG (WINAPI*)(UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, + DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*)) + GetProcAddress(user32, "QueryDisplayConfig"); + if (!pQueryDisplayConfig) + return; + + displayconfig_loaded = true; +} + +static int get_config(void *ctx, + UINT32 *num_paths, DISPLAYCONFIG_PATH_INFO** paths, + UINT32 *num_modes, DISPLAYCONFIG_MODE_INFO** modes) +{ + LONG res; + *paths = NULL; + *modes = NULL; + + // The display configuration could change between the call to + // GetDisplayConfigBufferSizes and the call to QueryDisplayConfig, so call + // them in a loop until the correct buffer size is chosen + do { + res = pGetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, num_paths, + num_modes); + if (res != ERROR_SUCCESS) + goto fail; + + // Free old buffers if they exist and allocate new ones + talloc_free(*paths); + talloc_free(*modes); + *paths = talloc_array(ctx, DISPLAYCONFIG_PATH_INFO, *num_paths); + *modes = talloc_array(ctx, DISPLAYCONFIG_MODE_INFO, *num_modes); + + res = pQueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, num_paths, *paths, + num_modes, *modes, NULL); + } while (res == ERROR_INSUFFICIENT_BUFFER); + if (res != ERROR_SUCCESS) + goto fail; + + return 0; +fail: + talloc_free(*paths); + talloc_free(*modes); + return -1; +} + +static DISPLAYCONFIG_PATH_INFO *get_path(UINT32 num_paths, + DISPLAYCONFIG_PATH_INFO* paths, + const wchar_t *device) +{ + // Search for a path with a matching device name + for (UINT32 i = 0; i < num_paths; i++) { + // Send a GET_SOURCE_NAME request + DISPLAYCONFIG_SOURCE_DEVICE_NAME source = { + .header = { + .size = sizeof source, + .type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, + .adapterId = paths[i].sourceInfo.adapterId, + .id = paths[i].sourceInfo.id, + } + }; + if (pDisplayConfigGetDeviceInfo(&source.header) != ERROR_SUCCESS) + return NULL; + + // Check if the device name matches + if (!wcscmp(device, source.viewGdiDeviceName)) + return &paths[i]; + } + + return NULL; +} + +static double get_refresh_rate_from_mode(DISPLAYCONFIG_MODE_INFO *mode) +{ + if (mode->infoType != DISPLAYCONFIG_MODE_INFO_TYPE_TARGET) + return 0.0; + + DISPLAYCONFIG_VIDEO_SIGNAL_INFO *info = + &mode->targetMode.targetVideoSignalInfo; + if (info->vSyncFreq.Denominator == 0) + return 0.0; + + return ((double)info->vSyncFreq.Numerator) / + ((double)info->vSyncFreq.Denominator); +} + +double mp_w32_displayconfig_get_refresh_rate(const wchar_t *device) +{ + // Load Windows 7 DisplayConfig API + pthread_once(&displayconfig_load_ran, displayconfig_load); + if (!displayconfig_loaded) + return 0.0; + + void *ctx = talloc_new(NULL); + double freq = 0.0; + + // Get the current display configuration + UINT32 num_paths; + DISPLAYCONFIG_PATH_INFO* paths; + UINT32 num_modes; + DISPLAYCONFIG_MODE_INFO* modes; + if (get_config(ctx, &num_paths, &paths, &num_modes, &modes)) + goto end; + + // Get the path for the specified monitor + DISPLAYCONFIG_PATH_INFO* path; + if (!(path = get_path(num_paths, paths, device))) + goto end; + + // Try getting the refresh rate from the mode first. The value in the mode + // overrides the value in the path. + if (path->targetInfo.modeInfoIdx != DISPLAYCONFIG_PATH_MODE_IDX_INVALID) + freq = get_refresh_rate_from_mode(&modes[path->targetInfo.modeInfoIdx]); + + // If the mode didn't contain a valid refresh rate, try the path + if (freq == 0.0 && path->targetInfo.refreshRate.Denominator != 0) { + freq = ((double)path->targetInfo.refreshRate.Numerator) / + ((double)path->targetInfo.refreshRate.Denominator); + } + +end: + talloc_free(ctx); + return freq; +} diff --git a/video/out/win32/displayconfig.h b/video/out/win32/displayconfig.h new file mode 100644 index 0000000000..ee6cd037f0 --- /dev/null +++ b/video/out/win32/displayconfig.h @@ -0,0 +1,27 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MP_WIN32_DISPLAYCONFIG_H_ +#define MP_WIN32_DISPLAYCONFIG_H_ + +#include <wchar.h> + +// Given a GDI monitor device name, get the precise refresh rate using the +// Windows 7 DisplayConfig API. Returns 0.0 on failure. +double mp_w32_displayconfig_get_refresh_rate(const wchar_t *device); + +#endif diff --git a/waftools/detections/compiler.py b/waftools/detections/compiler.py index d3e6b34455..284a7a5b9e 100644 --- a/waftools/detections/compiler.py +++ b/waftools/detections/compiler.py @@ -49,7 +49,7 @@ def __add_clang_flags__(ctx): "-Wno-tautological-constant-out-of-range-compare" ] def __add_mswin_flags__(ctx): - ctx.env.CFLAGS += ['-D_WIN32_WINNT=0x600', '-DUNICODE', '-DCOBJMACROS', + ctx.env.CFLAGS += ['-D_WIN32_WINNT=0x0601', '-DUNICODE', '-DCOBJMACROS', '-U__STRICT_ANSI__'] def __add_mingw_flags__(ctx): diff --git a/wscript_build.py b/wscript_build.py index 0bb0bc0620..d33c07db68 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -355,6 +355,7 @@ def build(ctx): ( "video/out/vo_x11.c" , "x11" ), ( "video/out/vo_xv.c", "xv" ), ( "video/out/w32_common.c", "win32" ), + ( "video/out/win32/displayconfig.c", "win32" ), ( "video/out/wayland_common.c", "wayland" ), ( "video/out/wayland/buffer.c", "wayland" ), ( "video/out/wayland/memfile.c", "wayland" ), |