[hidpi] Implement full HiDPI support

This converts the HiDPIAware into a series of helper functions, using
wxWidgets native support starting with wxWidgets 3.1.4. Custom
implementations are provided for Mac and Windows.

In addition, this fixes a few issues with the Windows build:
* Use the same defines variables in the MSVC and MinGW builds, with
  g++ and Clang.
* Add a custom manifest for the application, indicating full DPI
  support and support for recent Windows versions so the application
  will no longer run in compatibility mode by default.
* Manually spawn a console in debug builds if none is attached and
  always use the parent console if it is available otherwise, even in
  release builds. This also removes the /subsystem:console linker
  argument.
This commit is contained in:
Fabrice de Gans 2022-10-08 20:29:10 -07:00 committed by Rafael Kitover
parent 95337da558
commit 0d86432a31
11 changed files with 274 additions and 129 deletions

View File

@ -89,6 +89,11 @@ if(NOT WIN32 AND NOT APPLE)
endif()
endif()
# Win32 definitions common to all toolchains.
if (WIN32)
add_definitions(-D_UNICODE -DUNICODE -DwxUSE_GUI=1 -D__WXMSW__ -DWINVER=0x0A00 -DNTDDI_VERSION=0x0A000007)
endif()
# on VS with vcpkg we can't use FindwxWidgets, we have to set everything up
# manually because the package is broken
if(WIN32 AND CMAKE_TOOLCHAIN_FILE MATCHES vcpkg AND (X86_32 OR X86_64))
@ -120,8 +125,6 @@ if(WIN32 AND CMAKE_TOOLCHAIN_FILE MATCHES vcpkg AND (X86_32 OR X86_64))
add_definitions(-DWXUSINGDLL)
endif()
add_definitions(-D_UNICODE -DUNICODE -DwxUSE_GUI=1 -D__WXMSW__ -DwxUSE_RC_MANIFEST -DwxUSE_DPI_AWARE_MANIFEST=2)
set(common_prefix ${_VCPKG_INSTALLED_DIR}/${WINARCH}-windows${arch_suffix})
set(dbg_prefix ${_VCPKG_INSTALLED_DIR}/${WINARCH}-windows${arch_suffix}/debug)
set(installed_prefix ${_VCPKG_INSTALLED_DIR}/${WINARCH}-windows${arch_suffix}/${path_prefix})
@ -755,9 +758,17 @@ set(
set(ALL_SRC_WX ${SRC_WX})
list(APPEND ALL_SRC_WX ${CMAKE_CURRENT_SOURCE_DIR}/macsupport.mm)
list(APPEND ALL_SRC_WX ${CMAKE_CURRENT_SOURCE_DIR}/widgets/dpi-support-mac.mm)
list(APPEND ALL_SRC_WX ${CMAKE_CURRENT_SOURCE_DIR}/widgets/dpi-support-win.cpp)
list(APPEND ALL_SRC_WX ${CMAKE_CURRENT_SOURCE_DIR}/widgets/dpi-support.cpp)
if(APPLE)
list(APPEND SRC_WX ${CMAKE_CURRENT_SOURCE_DIR}/macsupport.mm)
list(APPEND SRC_WX macsupport.mm)
list(APPEND SRC_WX widgets/dpi-support-mac.mm)
elseif(WIN32)
list(APPEND SRC_WX widgets/dpi-support-win.cpp)
else()
list(APPEND SRC_WX widgets/dpi-support.cpp)
endif()
set(
@ -776,6 +787,7 @@ set(
wayland.h
wxutil.h
config/user-input.h
widgets/dpi-support.h
widgets/wx/gamecontrol.h
widgets/wx/keyedit.h
widgets/wx/joyedit.h
@ -976,18 +988,23 @@ if(NOT TRANSLATIONS_ONLY)
endif()
endif()
# link libgcc/libstdc++ statically on mingw
# and adjust link command when making a static binary
if(CMAKE_COMPILER_IS_GNUCXX)
if(WIN32)
# Build a console app in debug mode on Windows
if(CMAKE_BUILD_TYPE MATCHES "^(Debug|RelWithDebInfo)$")
set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -Wl,--subsystem,console")
else()
set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -Wl,--subsystem,windows")
# This is necessary for DPI support and is not included by default in
# the msys2 build.
target_link_libraries(visualboyadvance-m shcore)
if(MSVC)
# the debug lib libcmtd is linked in debug mode, so don't link the normal version
set_target_properties(visualboyadvance-m PROPERTIES LINK_FLAGS_DEBUG "/nodefaultlib:libcmt")
# Disable the auto-generated manifest from CMake.
target_link_options(visualboyadvance-m PRIVATE "/MANIFEST:NO")
endif()
endif()
# link libgcc/libstdc++ statically on mingw
# and adjust link command when making a static binary
if(CMAKE_COMPILER_IS_GNUCXX)
if(VBAM_STATIC)
# some dists don't have a static libpthread
set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread ")
@ -1006,20 +1023,8 @@ if(NOT TRANSLATIONS_ONLY)
)
endif()
endif()
elseif(MSVC)
# the debug lib libcmtd is linked in debug mode, so don't link the normal version
set_target_properties(visualboyadvance-m PROPERTIES LINK_FLAGS_DEBUG "/nodefaultlib:libcmt /subsystem:console")
# Disable the auto-generated manifest from CMake.
target_link_options(visualboyadvance-m PRIVATE "/MANIFEST:NO")
endif()
# Make the app a console app in debug mode to get log messages.
if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL Debug)
target_compile_definitions(visualboyadvance-m PRIVATE -DWIN32_CONSOLE_APP)
endif()
if(NOT WIN32 AND NOT APPLE)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/visualboyadvance-m.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/visualboyadvance-m.appdata.xml DESTINATION ${CMAKE_INSTALL_PREFIX}/share/appdata)

View File

@ -7,41 +7,6 @@
#include "wxvbam.h"
#include "drawing.h"
double HiDPIAware::HiDPIScaleFactor()
{
if (hidpi_scale_factor == 0) {
NSWindow* window = [(NSView*)GetWindow()->GetHandle() window];
if ([window respondsToSelector:@selector(backingScaleFactor)]) {
hidpi_scale_factor = [window backingScaleFactor];
}
else {
hidpi_scale_factor = 1.0;
}
}
return hidpi_scale_factor;
}
void HiDPIAware::RequestHighResolutionOpenGLSurface()
{
NSView* view = (NSView*)(GetWindow()->GetHandle());
if ([view respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
[view setWantsBestResolutionOpenGLSurface:YES];
}
}
void HiDPIAware::GetRealPixelClientSize(int* x, int* y)
{
NSView* view = (NSView*)(GetWindow()->GetHandle());
NSSize backing_size = [view convertSizeToBacking:view.bounds.size];
*x = backing_size.width;
*y = backing_size.height;
}
Quartz2DDrawingPanel::Quartz2DDrawingPanel(wxWindow* parent, int _width, int _height)
: BasicDrawingPanel(parent, _width, _height)
{

View File

@ -54,6 +54,7 @@ GameArea::GameArea()
, basic_width(GBAWidth)
, basic_height(GBAHeight)
, fullscreen(false)
, dpi_scale_factor_(widgets::DPIScaleFactorForWindow(this))
, paused(false)
, pointer_blanked(false)
, mouse_active_time(0)
@ -745,12 +746,11 @@ void GameArea::DelBorder()
void GameArea::AdjustMinSize()
{
wxWindow* frame = wxGetApp().frame;
double hidpi_scale_factor = HiDPIScaleFactor();
// note: could safely set min size to 1x or less regardless of video_scale
// but setting it to scaled size makes resizing to default easier
wxSize sz((std::ceil(basic_width * gopts.video_scale) / hidpi_scale_factor),
(std::ceil(basic_height * gopts.video_scale) / hidpi_scale_factor));
wxSize sz((std::ceil(basic_width * gopts.video_scale) * dpi_scale_factor_),
(std::ceil(basic_height * gopts.video_scale) * dpi_scale_factor_));
SetMinSize(sz);
#if wxCHECK_VERSION(2, 8, 8)
sz = frame->ClientToWindowSize(sz);
@ -763,10 +763,9 @@ void GameArea::AdjustMinSize()
void GameArea::LowerMinSize()
{
wxWindow* frame = wxGetApp().frame;
double hidpi_scale_factor = HiDPIScaleFactor();
wxSize sz(std::ceil(basic_width / hidpi_scale_factor),
std::ceil(basic_height / hidpi_scale_factor));
wxSize sz(std::ceil(basic_width * dpi_scale_factor_),
std::ceil(basic_height * dpi_scale_factor_));
SetMinSize(sz);
// do not take decorations into account
@ -780,9 +779,8 @@ void GameArea::AdjustSize(bool force)
if (fullscreen)
return;
double hidpi_scale_factor = HiDPIScaleFactor();
const wxSize newsz((std::ceil(basic_width * gopts.video_scale) / hidpi_scale_factor),
(std::ceil(basic_height * gopts.video_scale) / hidpi_scale_factor));
const wxSize newsz((std::ceil(basic_width * gopts.video_scale) * dpi_scale_factor_),
(std::ceil(basic_height * gopts.video_scale) * dpi_scale_factor_));
if (!force) {
wxSize sz = GetClientSize();
@ -961,6 +959,16 @@ void GameArea::OnKillFocus(wxFocusEvent& ev)
ev.Skip();
}
#if WX_HAS_NATIVE_HI_DPI_SUPPORT
void GameArea::OnDpiChanged(wxDPIChangedEvent&) {
if (dpi_scale_factor_ == GetDPIScaleFactor()) {
return;
}
dpi_scale_factor_ = GetDPIScaleFactor();
AdjustSize(true);
}
#endif // WX_HAS_NATIVE_HI_DPI_SUPPORT
void GameArea::Pause()
{
if (paused)
@ -1332,6 +1340,9 @@ void GameArea::OnSDLJoy(wxJoyEvent& ev)
BEGIN_EVENT_TABLE(GameArea, wxPanel)
EVT_IDLE(GameArea::OnIdle)
EVT_SDLJOY(GameArea::OnSDLJoy)
#if WX_HAS_NATIVE_HI_DPI_SUPPORT
EVT_DPI_CHANGED(GameArea::OnDpiChanged)
#endif // WX_HAS_NATIVE_HI_DPI_SUPPORT
// FIXME: wxGTK does not generate motion events in MainFrame (not sure
// what to do about it)
EVT_MOUSE_EVENTS(GameArea::MouseEvent)
@ -2086,7 +2097,7 @@ GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height)
, wxglc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetClientSize(),
wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
{
RequestHighResolutionOpenGLSurface();
widgets::RequestHighResolutionOpenGlSurfaceForWindow(this);
#ifndef wxGL_IMPLICIT_CONTEXT
ctx = new wxGLContext(this);
SetCurrent(*ctx);
@ -2251,7 +2262,7 @@ void GLDrawingPanel::AdjustViewport()
#endif
int x, y;
GetRealPixelClientSize(&x, &y);
widgets::GetRealPixelClientSize(this, &x, &y);
glViewport(0, 0, x, y);
#ifdef DEBUG
@ -2514,24 +2525,3 @@ void GameArea::ShowMenuBar()
menu_bar_hidden = false;
#endif
}
// stub HiDPI methods, see macsupport.mm for the Mac support
#ifndef __WXMAC__
double HiDPIAware::HiDPIScaleFactor()
{
if (hidpi_scale_factor == 0) {
hidpi_scale_factor = 1.0;
}
return hidpi_scale_factor;
}
void HiDPIAware::RequestHighResolutionOpenGLSurface()
{
}
void HiDPIAware::GetRealPixelClientSize(int* x, int* y)
{
GetWindow()->GetClientSize(x, y);
}
#endif // HiDPI stubs

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, system</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@ -0,0 +1,47 @@
#include "widgets/dpi-support.h"
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#include <wx/window.h>
namespace widgets {
double DPIScaleFactorForWindow(wxWindow* window) {
#if WX_HAS_NATIVE_HI_DPI_SUPPORT
return window->GetDPIScaleFactor();
#else
NSWindow* ns_window = [(NSView*)window->GetHandle() window];
if ([ns_window respondsToSelector:@selector(backingScaleFactor)]) {
return [ns_window backingScaleFactor];
}
return 1.0;
#endif // WX_HAS_NATIVE_HI_DPI_SUPPORT
}
void RequestHighResolutionOpenGlSurfaceForWindow(wxWindow* window) {
#if !WX_HAS_NATIVE_HI_DPI_SUPPORT
NSView* view = (NSView*)(window->GetHandle());
if ([view respondsToSelector:@selector
(setWantsBestResolutionOpenGLSurface:)]) {
[view setWantsBestResolutionOpenGLSurface:YES];
}
#endif // !WX_HAS_NATIVE_HI_DPI_SUPPORT
}
void GetRealPixelClientSize(wxWindow* window, int* x, int* y) {
#if WX_HAS_NATIVE_HI_DPI_SUPPORT
window->GetClientSize(x, y);
#else
NSView* view = (NSView*)(window->GetHandle());
NSSize backing_size = [view convertSizeToBacking:view.bounds.size];
*x = backing_size.width;
*y = backing_size.height;
#endif // WX_HAS_NATIVE_HI_DPI_SUPPORT
}
} // namespace widgets

View File

@ -0,0 +1,55 @@
#include "widgets/dpi-support.h"
#include <Windows.h>
#include <VersionHelpers.h>
#include <shellscalingapi.h>
#include <wx/window.h>
namespace widgets {
double DPIScaleFactorForWindow(wxWindow* window) {
#if WX_HAS_NATIVE_HI_DPI_SUPPORT
return window->GetDPIScaleFactor();
#else
static constexpr double kStandardDpi = 96.0;
HWND wnd = window->GetHWND();
if (IsWindows10OrGreater()) {
// We can't properly resize on DPI/Monitor change, but neither can
// wxWidgets so we're consistent.
UINT dpi = GetDpiForWindow(wnd);
if (dpi != 0) {
return static_cast<int>(dpi) / kStandardDpi;
}
}
if (IsWindows8Point1OrGreater()) {
HMONITOR monitor = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
UINT xdpi;
UINT ydpi;
HRESULT success =
GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi);
if (success == S_OK) {
return static_cast<int>(ydpi) / kStandardDpi;
}
}
HDC dc = GetDC(wnd);
if (dc == nullptr) {
return 1.0;
}
int ydpi = GetDeviceCaps(dc, LOGPIXELSY);
ReleaseDC(nullptr, dc);
return ydpi / kStandardDpi;
#endif // WX_HAS_NATIVE_HI_DPI_SUPPORT
}
void RequestHighResolutionOpenGlSurfaceForWindow(wxWindow*) {}
void GetRealPixelClientSize(wxWindow* window, int* x, int* y) {
window->GetClientSize(x, y);
}
} // namespace widgets

View File

@ -0,0 +1,21 @@
#include "widgets/dpi-support.h"
#include <wx/window.h>
namespace widgets {
double DPIScaleFactorForWindow(wxWindow* window) {
#if WX_HAS_NATIVE_HI_DPI_SUPPORT
return window->GetDPIScaleFactor();
#else
return window->GetContentScaleFactor();
#endif // WX_HAS_NATIVE_HI_DPI_SUPPORT
}
void RequestHighResolutionOpenGlSurfaceForWindow(wxWindow*) {}
void GetRealPixelClientSize(wxWindow* window, int* x, int* y) {
window->GetClientSize(x, y);
}
} // namespace widgets

View File

@ -0,0 +1,23 @@
#ifndef VBAM_WX_WIDGETS_DPI_SUPPORT_
#define VBAM_WX_WIDGETS_DPI_SUPPORT_
#include <wx/version.h>
#if wxCHECK_VERSION(3, 1, 4)
#define WX_HAS_NATIVE_HI_DPI_SUPPORT 1
#else
#define WX_HAS_NATIVE_HI_DPI_SUPPORT 0
#endif // wxCHECK_VERSION(3,1,4)
// Forward declaration.
class wxWindow;
namespace widgets {
double DPIScaleFactorForWindow(wxWindow* window);
void RequestHighResolutionOpenGlSurfaceForWindow(wxWindow* window);
void GetRealPixelClientSize(wxWindow* window, int* x, int* y);
} // namespace widgets
#endif // VBAM_WX_WIDGETS_DPI_SUPPORT_

View File

@ -5,6 +5,10 @@
#include "wxvbam.h"
#ifdef __WXMSW__
#include <windows.h>
#endif
#include <stdio.h>
#include <wx/cmdline.h>
#include <wx/file.h>
@ -35,15 +39,6 @@
IMPLEMENT_APP(wxvbamApp)
IMPLEMENT_DYNAMIC_CLASS(MainFrame, wxFrame)
#ifdef WIN32_CONSOLE_APP
#include <windows.h>
int main(int argc, char** argv)
{
return WinMain(::GetModuleHandle(NULL), 0, 0, 0);
}
#endif
#ifndef NO_ONLINEUPDATES
#include "autoupdater/autoupdater.h"
#endif // NO_ONLINEUPDATES
@ -199,28 +194,36 @@ wxString wxvbamApp::GetAbsolutePath(wxString path)
return path;
}
#ifdef __WXMSW__
#include <wx/msw/private.h>
#include <windows.h>
#endif
bool wxvbamApp::OnInit()
{
// set up logging
#ifndef NDEBUG
wxLog::SetLogLevel(wxLOG_Trace);
#endif
#endif // !NDEBUG
#ifdef __WXMSW__
// in windows console mode debug builds, redirect e.g. --help to stderr
#ifndef NDEBUG
wxMessageOutput::Set(new wxMessageOutputStderr());
#endif
// turn off output buffering to support windows consoles
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
// redirect stderr to stdout
dup2(1, 2);
#endif
#endif // !NDEBUG
bool console_attached = AttachConsole(ATTACH_PARENT_PROCESS) != FALSE;
#ifndef NDEBUG
// In debug builds, create a console if none is attached.
if (!console_attached) {
console_attached = AllocConsole() != FALSE;
}
#endif // !NDEBUG
// Redirect stdout/stderr to the console if one is attached.
// This code was taken from Dolphin.
// https://github.com/dolphin-emu/dolphin/blob/6cf99195c645f54d54c72322ad0312a0e56bc985/Source/Core/DolphinQt/Main.cpp#L112
HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_attached && stdout_handle) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#endif // __WXMSW__
using_wayland = IsItWayland();
// use consistent names for config

View File

@ -11,6 +11,7 @@
#include <wx/propdlg.h>
#include <wx/datetime.h>
#include "widgets/dpi-support.h"
#include "wx/joyedit.h"
#include "wx/keyedit.h"
#include "wx/sdljoy.h"
@ -388,18 +389,6 @@ private:
#include "cmdhandlers.h"
};
// helper class to add HiDPI awareness (mostly for Mac OS X)
class HiDPIAware {
public:
HiDPIAware() { hidpi_scale_factor = 0; }
virtual double HiDPIScaleFactor();
virtual void RequestHighResolutionOpenGLSurface();
virtual void GetRealPixelClientSize(int* x, int* y);
virtual wxWindow* GetWindow() = 0;
private:
double hidpi_scale_factor;
};
// a class for polling joystick keys
class JoystickPoller : public wxTimer {
public:
@ -519,7 +508,7 @@ class DrawingPanelBase;
#include <windows.h>
#endif
class GameArea : public wxPanel, public HiDPIAware {
class GameArea : public wxPanel {
public:
GameArea();
virtual ~GameArea();
@ -641,8 +630,6 @@ public:
void StartGamePlayback(const wxString& fname);
void StopGamePlayback();
virtual wxWindow* GetWindow() { return this; }
protected:
MainFrame* main_frame;
@ -655,6 +642,7 @@ protected:
int basic_width, basic_height;
bool fullscreen;
double dpi_scale_factor_ = 0;
bool paused;
void OnIdle(wxIdleEvent&);
@ -665,6 +653,9 @@ protected:
void EraseBackground(wxEraseEvent& ev);
void OnSize(wxSizeEvent& ev);
void OnKillFocus(wxFocusEvent& ev);
#if WX_HAS_NATIVE_HI_DPI_SUPPORT
void OnDpiChanged(wxDPIChangedEvent& ev);
#endif // WX_HAS_NATIVE_HI_DPI_SUPPORT
#ifndef NO_FFMPEG
recording::MediaRecorder snd_rec, vid_rec;
@ -716,7 +707,7 @@ extern bool cmditem_lt(const struct cmditem& cmd1, const struct cmditem& cmd2);
class FilterThread;
class DrawingPanelBase : public HiDPIAware {
class DrawingPanelBase {
public:
DrawingPanelBase(int _width, int _height);
~DrawingPanelBase();

View File

@ -2,6 +2,7 @@
AAAAA_MAINICON ICON "icons/visualboyadvance-m.ico"
#define wxUSE_NO_MANIFEST 1
#include "wx/msw/wx.rc"
#include "version.h"
@ -23,6 +24,8 @@ WINSPARKLE_DLL_RC RCDATA WINSPARKLE_DLL_PATH
#endif /* NO_ONLINEUPDATES */
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "visualboyadvance-m.manifest"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION