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.dpiouJ2D_UISCALE. - Applique via
sun.java2d.uiScaleavant qu'AWT ne s'initialise. - Déjà câblé quand on dépend de
graalvm-runtime—GraalVmInitializer.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é | Source | Description |
|---|---|---|
| 1 | J2D_UISCALE | Override JVM explicite (variable d'env) |
| 2 | GSettings | GNOME org.gnome.desktop.interface → scaling-factor (via libgio) |
| 3 | GDK_SCALE | Échelle entière de session GTK / GNOME |
| 4 | GDK_DPI_SCALE | Multiplicateur DPI fractionnaire GTK |
| 5 | Xft.dpi | X 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
| API | Description |
|---|---|
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.
SSL natif — les racines de confiance depuis l'OS, pas le JRE
Un X509TrustManager qui merge les défauts JVM avec le keychain / cert store de l'OS, pour que les racines d'entreprise et CAs installés par MDM marchent simplement.
Un DSL, seize installateurs
Chaque format d'installateur que les utilisateurs attendent, déclaré une fois dans Gradle.