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 #endif
static void SetHighDPIAttributes() { static void SetHighDPIAttributes() {
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
#ifdef _WIN32 #ifdef _WIN32
// For Windows, we want to avoid scaling artifacts on fractional scaling ratios. // Windows logic: Set policy globally.
// This is done by setting the optimal scaling policy for the primary screen. // 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(); SetProcessDPIAware();
// For Windows 10+, use Per Monitor v2 DPI Awareness
// This provides better scaling for multi-monitor setups
HMODULE shcore = LoadLibrary(L"shcore.dll"); HMODULE shcore = LoadLibrary(L"shcore.dll");
if (shcore) { if (shcore) {
typedef HRESULT(WINAPI* SetProcessDpiAwarenessFunc)(int); typedef HRESULT(WINAPI* SetProcessDpiAwarenessFunc)(int);
SetProcessDpiAwarenessFunc setProcessDpiAwareness = SetProcessDpiAwarenessFunc setProcessDpiAwareness =
(SetProcessDpiAwarenessFunc)GetProcAddress(shcore, "SetProcessDpiAwareness"); (SetProcessDpiAwarenessFunc)GetProcAddress(shcore, "SetProcessDpiAwareness");
if (setProcessDpiAwareness) { if (setProcessDpiAwareness) {
// PROCESS_PER_MONITOR_DPI_AWARE_V2 = 2 setProcessDpiAwareness(2); // PROCESS_PER_MONITOR_DPI_AWARE_V2
setProcessDpiAwareness(2);
} }
FreeLibrary(shcore); 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 #endif
} }
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
// 1. Detect Gamescope/Steam Deck hardware // 1. Detect Gamescope/Steam Deck hardware
const bool is_gamescope = qgetenv("XDG_CURRENT_DESKTOP") == "gamescope" || const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() ||
!qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope" ||
!qgetenv("STEAM_DECK").isEmpty(); !qgetenv("STEAM_DECK").isEmpty();
if (is_gamescope) { if (is_gamescope) {
// Kill the SteamOS scaling requests before they can bloat the UI // Kill the SteamOS scaling requests before they can bloat the UI
QGuiApplication::setDesktopSettingsAware(false); QGuiApplication::setDesktopSettingsAware(false);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Floor);
// Force 1:1 pixel ratio // Force 1:1 pixel ratio
qputenv("QT_ENABLE_HIGHDPI_SCALING", "0"); qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
qputenv("QT_SCALE_FACTOR", "1"); qputenv("QT_SCALE_FACTOR", "1");
qputenv("QT_SCREEN_SCALE_FACTORS", "1"); qputenv("QT_SCREEN_SCALE_FACTORS", "1");
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0"); 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"); 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 // Ensure Gamescope compositor handles Citron menus correctly
QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs); QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs);
qputenv("QT_WAYLAND_SHELL_INTEGRATION", "xdg-shell"); qputenv("QT_WAYLAND_SHELL_INTEGRATION", "xdg-shell");
} }
// 2. NOW setup AppImage environment // 2. Setup AppImage environment
const bool is_appimage = !qgetenv("APPIMAGE").isEmpty(); const bool is_appimage = !qgetenv("APPIMAGE").isEmpty();
if (is_appimage) { if (is_appimage) {
qputenv("QT_WAYLAND_DISABLE_EXPLICIT_SYNC", "1"); qputenv("QT_WAYLAND_DISABLE_EXPLICIT_SYNC", "1");
@@ -6231,6 +6206,7 @@ int main(int argc, char* argv[]) {
QGuiApplication::setDesktopFileName(QStringLiteral("org.citron_emu.citron")); QGuiApplication::setDesktopFileName(QStringLiteral("org.citron_emu.citron"));
#endif #endif
// Call policy attributes BEFORE creating the real QApplication instance
SetHighDPIAttributes(); SetHighDPIAttributes();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
@@ -6252,21 +6228,16 @@ int main(int argc, char* argv[]) {
#ifdef _WIN32 #ifdef _WIN32
// On Windows, updates are applied by the helper script after the app exits. // 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"; std::filesystem::path staging_path = app_dir / "update_staging";
if (std::filesystem::exists(staging_path)) { if (std::filesystem::exists(staging_path)) {
try { try {
std::filesystem::remove_all(staging_path); std::filesystem::remove_all(staging_path);
} catch (...) { } catch (...) {}
// Ignore cleanup errors
}
} }
#else #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::HasStagedUpdate(app_dir)) {
if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) { if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) {
// Show a simple message that update was applied
QMessageBox::information(nullptr, QObject::tr("Update Applied"), QMessageBox::information(nullptr, QObject::tr("Update Applied"),
QObject::tr("Citron has been updated successfully!")); QObject::tr("Citron has been updated successfully!"));
} }
@@ -6279,10 +6250,7 @@ int main(int argc, char* argv[]) {
#endif #endif
// Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021 // Workaround for QTBUG-85409
// 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
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
const QLocale locale = QLocale::system(); const QLocale locale = QLocale::system();
if (QStringLiteral("\u3008") == locale.toString(1)) { if (QStringLiteral("\u3008") == locale.toString(1)) {
@@ -6314,8 +6282,7 @@ int main(int argc, char* argv[]) {
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
&GMainWindow::OnAppFocusStateChanged); &GMainWindow::OnAppFocusStateChanged);
int result = app.exec(); return app.exec();
return result;
} }
void GMainWindow::OnCheckForUpdates() { void GMainWindow::OnCheckForUpdates() {