Nucleus
Performance & native

HiDPI Linux — détection native du facteur d'échelle pour AWT/JBR

L'OpenJDK standard ne détecte pas le facteur d'échelle d'affichage Linux. linux-hidpi lit GSettings, GDK_SCALE et Xft.dpi via JNI et applique le bon ui-scale avant qu'AWT ne s'initialise.

L'OpenJDK standard sous Linux ignore le facteur d'échelle natif — sur un écran HiDPI, la fenêtre Compose Desktop s'affiche en 1x, minuscule et floue. JetBrains Runtime gère ça en interne ; partout ailleurs on doit le faire manuellement. linux-hidpi reflète la logique de détection de systemScale.c de JBR dans un bridge JNI, pour que n'importe quelle JVM — y compris GraalVM Native Image — ait le bon DPI.

TL;DR

  • Détecte le facteur d'échelle Linux depuis GSettings, GDK_SCALE, GDK_DPI_SCALE, Xft.dpi ou J2D_UISCALE.
  • Applique via sun.java2d.uiScale avant qu'AWT ne s'initialise.
  • Déjà câblé quand on dépend de graalvm-runtimeGraalVmInitializer.initialize() l'appelle automatiquement.
  • Le backend Tao a son propre chemin HiDPI — voir /docs/tao/hidpi.

Utilisateurs JBR : on n'en as pas besoin

Si le app tourne sur JetBrains Runtime (le défaut du chemin AWT de nucleus-application), JBR gère le scaling par moniteur nativement. linux-hidpi n'est utile que pour OpenJDK standard et GraalVM Native Image, où JBR n'est pas une option.

Install

dependencies {
    implementation("dev.nucleusframework:nucleus.linux-hidpi:<version>")
}

Il est possible de sauter cette dépendance quand on dépend déjà de graalvm-runtime — il tire linux-hidpi transitivement et GraalVmInitializer.initialize() l'invoque.

Quickstart

import dev.nucleusframework.hidpi.getLinuxNativeScaleFactor
import androidx.compose.ui.window.application

fun main() {
    if (System.getProperty("sun.java2d.uiScale") == null) {
        val scale = getLinuxNativeScaleFactor()
        if (scale > 0.0) {
            System.setProperty("sun.java2d.uiScale", scale.toString())
        }
    }

    application {
        // l'app Compose Desktop
    }
}

La fonction est un no-op (renvoie 0.0) sur les plateformes non-Linux — laisse l'appel en place, il ne coûte rien sous macOS/Windows.

Ou, si l'on ne veux pas du check de propriété système, en une ligne :

import dev.nucleusframework.hidpi.applyLinuxHiDpiScale

fun main() {
    applyLinuxHiDpiScale()
    application { /* ... */ }
}

Comment ça marche

getLinuxNativeScaleFactor() parcourt cinq sources de détection par ordre de priorité :

PrioritéSourceDescription
1J2D_UISCALEOverride JVM explicite (variable d'env)
2GSettingsGNOME org.gnome.desktop.interfacescaling-factor (via libgio)
3GDK_SCALEÉchelle entière de session GTK / GNOME
4GDK_DPI_SCALEMultiplicateur DPI fractionnaire GTK
5Xft.dpiX Resource Manager (KDE, GNOME legacy, …)

Le côté natif utilise dlopen pour charger libgio et libX11 au runtime — il n'y a pas de dépendance dure au link autre que libc, donc le binaire tourne aussi dans des conteneurs minimaux. Si la lib JNI échoue à se charger complètement (Alpine musl sans glibc, ou un sandbox sans /proc), la fonction retombe sur un parsing pure-Java de J2D_UISCALE, GDK_SCALE et GDK_DPI_SCALE.

Le scaling par moniteur sous Linux est une cible mouvante — les compositeurs Wayland le font différemment de X11, et AWT ne propage pas la valeur par moniteur via GraphicsConfiguration. Pour une vraie expérience par moniteur sous Linux, livre plutôt le backend Tao — il parle au compositeur directement.

Binaires natifs

Embarqués pré-buildés dans le JAR :

  • Linux x64 : libnucleus_linux_hidpi_jni.so
  • Linux aarch64 : libnucleus_linux_hidpi_jni.so

Extraits automatiquement par NativeLibraryLoader de core-runtime à la première utilisation.

Référence

APIDescription
fun getLinuxNativeScaleFactor(): DoubleÉchelle résolue (par ex. 1.0, 1.25, 2.0). 0.0 si la détection a échoué ou non-Linux.
fun applyLinuxHiDpiScale()Met sun.java2d.uiScale si non défini et que l'échelle détectée est > 0.

Contournements DPI fractionnaire

Le scaling fractionnaire de GNOME (125%, 150%) s'expose via GDK_DPI_SCALE. AWT ne parle pas le fractionnaire nativement — il arrondit à l'entier le plus proche. Si le texte est flou sur un écran à 125%, passe au backend Tao ou force un pipeline de rendu à 1.5x via GDK_SCALE=2 et GDK_DPI_SCALE=0.75.

Notes

ProGuard

-keep class dev.nucleusframework.hidpi.HiDpiLinuxBridge {
    native <methods>;
}

Le plugin Gradle Nucleus l'ajoute automatiquement.