fix(gamescope): DPI Re-architecture For GameScope Compatability

Signed-off-by: Collecting <collecting@noreply.localhost>
This commit is contained in:
Collecting
2026-01-04 22:00:08 +00:00
parent 7b83a79d16
commit 884922a1cb

View File

@@ -6089,96 +6089,71 @@ void VolumeButton::ResetMultiplier() {
#endif
static void SetHighDPIAttributes() {
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
#ifdef _WIN32
// For Windows, we want to avoid scaling artifacts on fractional scaling ratios.
// This is done by setting the optimal scaling policy for the primary screen.
// Windows logic: Set policy globally.
// removed the 'temp QApplication' here because in Qt 6 it locks the DPI logic
// before our environment overrides in main() can take effect.
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::Round);
// Create a temporary QApplication.
int temp_argc = 0;
char** temp_argv = nullptr;
QApplication temp{temp_argc, temp_argv};
// Get the current screen geometry.
const QScreen* primary_screen = QGuiApplication::primaryScreen();
if (primary_screen == nullptr) {
return;
}
const QRect screen_rect = primary_screen->geometry();
const int real_width = screen_rect.width();
const int real_height = screen_rect.height();
const float real_ratio = primary_screen->logicalDotsPerInch() / 96.0f;
// Recommended minimum width and height for proper window fit.
// Any screen with a lower resolution than this will still have a scale of 1.
constexpr float minimum_width = 1350.0f;
constexpr float minimum_height = 900.0f;
const float width_ratio = std::max(1.0f, real_width / minimum_width);
const float height_ratio = std::max(1.0f, real_height / minimum_height);
// Get the lower of the 2 ratios and truncate, this is the maximum integer scale.
const float max_ratio = std::trunc(std::min(width_ratio, height_ratio));
if (max_ratio > real_ratio) {
QApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::Round);
} else {
QApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
}
#else
// Other OSes should be better than Windows at fractional scaling.
QApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
// Set the DPI awareness for better scaling on Windows
#ifdef _WIN32
// Enable Per Monitor DPI Awareness for Windows 8.1+
SetProcessDPIAware();
// For Windows 10+, use Per Monitor v2 DPI Awareness
// This provides better scaling for multi-monitor setups
HMODULE shcore = LoadLibrary(L"shcore.dll");
if (shcore) {
typedef HRESULT(WINAPI* SetProcessDpiAwarenessFunc)(int);
SetProcessDpiAwarenessFunc setProcessDpiAwareness =
(SetProcessDpiAwarenessFunc)GetProcAddress(shcore, "SetProcessDpiAwareness");
if (setProcessDpiAwareness) {
// PROCESS_PER_MONITOR_DPI_AWARE_V2 = 2
setProcessDpiAwareness(2);
setProcessDpiAwareness(2); // PROCESS_PER_MONITOR_DPI_AWARE_V2
}
FreeLibrary(shcore);
}
#else
if (is_gamescope) {
// Force 1:1 pixel mapping for Steam Deck to prevent bloated windows.
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
} else {
// Standard Linux desktops handle fractional scaling better via PassThrough
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
}
#endif
}
int main(int argc, char* argv[]) {
// 1. Detect Gamescope/Steam Deck hardware
const bool is_gamescope = qgetenv("XDG_CURRENT_DESKTOP") == "gamescope" ||
!qgetenv("GAMESCOPE_WIDTH").isEmpty() ||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() ||
qgetenv("XDG_CURRENT_DESKTOP") == "gamescope" ||
!qgetenv("STEAM_DECK").isEmpty();
if (is_gamescope) {
// Kill the SteamOS scaling requests before they can bloat the UI
QGuiApplication::setDesktopSettingsAware(false);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
// Force 1:1 pixel ratio
qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
qputenv("QT_SCALE_FACTOR", "1");
qputenv("QT_SCREEN_SCALE_FACTORS", "1");
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0");
// Steam Deck has a high physical DPI. Hard-coding 96 DPI prevents text
// from being oversized in dialogs like "About" or "Updater".
qputenv("QT_FONT_DPI", "96");
// FORCE X11 backend: Qt 6 scaling overrides are reliably respected under XCB in Gamescope.
// Wayland mode in Gamescope often ignores scaling overrides for child windows.
qputenv("QT_QPA_PLATFORM", "xcb");
// Ensure Gamescope compositor handles Citron menus correctly
QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs);
qputenv("QT_WAYLAND_SHELL_INTEGRATION", "xdg-shell");
}
// 2. NOW setup AppImage environment
// 2. Setup AppImage environment
const bool is_appimage = !qgetenv("APPIMAGE").isEmpty();
if (is_appimage) {
qputenv("QT_WAYLAND_DISABLE_EXPLICIT_SYNC", "1");
@@ -6231,6 +6206,7 @@ int main(int argc, char* argv[]) {
QGuiApplication::setDesktopFileName(QStringLiteral("org.citron_emu.citron"));
#endif
// Call policy attributes BEFORE creating the real QApplication instance
SetHighDPIAttributes();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
@@ -6252,21 +6228,16 @@ int main(int argc, char* argv[]) {
#ifdef _WIN32
// On Windows, updates are applied by the helper script after the app exits.
// If we find a staging directory here, it means the helper script failed.
// Clean it up to avoid confusion.
std::filesystem::path staging_path = app_dir / "update_staging";
if (std::filesystem::exists(staging_path)) {
try {
std::filesystem::remove_all(staging_path);
} catch (...) {
// Ignore cleanup errors
}
} catch (...) {}
}
#else
// On Linux, apply staged updates at startup as before
// On Linux, apply staged updates at startup
if (Updater::UpdaterService::HasStagedUpdate(app_dir)) {
if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) {
// Show a simple message that update was applied
QMessageBox::information(nullptr, QObject::tr("Update Applied"),
QObject::tr("Citron has been updated successfully!"));
}
@@ -6279,10 +6250,7 @@ int main(int argc, char* argv[]) {
#endif
// Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021
// so we can see if we get \u3008 instead
// TL;DR all other number formats are consecutive in unicode code points
// This bug is fixed in Qt6, specifically 6.0.0-alpha1
// Workaround for QTBUG-85409
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
const QLocale locale = QLocale::system();
if (QStringLiteral("\u3008") == locale.toString(1)) {
@@ -6314,8 +6282,7 @@ int main(int argc, char* argv[]) {
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
&GMainWindow::OnAppFocusStateChanged);
int result = app.exec();
return result;
return app.exec();
}
void GMainWindow::OnCheckForUpdates() {