Skip to content

Commit 5f07ea1

Browse files
authored
DisplayService: Add system bar insets API (#435)
* Add SafeArea api * Add insets API to display service * remove unused insets * add empty line * add missing config file * Add implementation for iOS
1 parent 4d3f5e6 commit 5f07ea1

File tree

13 files changed

+182
-18
lines changed

13 files changed

+182
-18
lines changed

modules/display/src/main/java/com/gluonhq/attach/display/DisplayService.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2019 Gluon
2+
* Copyright (c) 2016, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -30,6 +30,7 @@
3030
import com.gluonhq.attach.util.Services;
3131
import javafx.beans.property.ReadOnlyObjectProperty;
3232
import javafx.geometry.Dimension2D;
33+
import javafx.geometry.Insets;
3334

3435
import java.util.Optional;
3536

@@ -162,4 +163,15 @@ static Optional<DisplayService> create() {
162163
* @since 3.8.0
163164
*/
164165
ReadOnlyObjectProperty<Notch> notchProperty();
166+
167+
/**
168+
* Property that contains the insets of the system bars (typically
169+
* the status bar at the top and the navigation bar at the bottom).
170+
* These insets can be used to add the necessary padding so the application
171+
* doesn't get underneath the content shown at the system bars.
172+
*
173+
* @return A read only property with the insets of the system bars
174+
*/
175+
ReadOnlyObjectProperty<Insets> systemBarsInsetsProperty();
176+
165177
}

modules/display/src/main/java/com/gluonhq/attach/display/impl/AndroidDisplayService.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2020 Gluon
2+
* Copyright (c) 2016, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -29,9 +29,11 @@
2929

3030
import com.gluonhq.attach.display.DisplayService;
3131
import com.gluonhq.attach.util.Util;
32+
import javafx.application.Platform;
3233
import javafx.beans.property.ReadOnlyObjectProperty;
3334
import javafx.beans.property.ReadOnlyObjectWrapper;
3435
import javafx.geometry.Dimension2D;
36+
import javafx.geometry.Insets;
3537
import javafx.geometry.Rectangle2D;
3638
import javafx.stage.Screen;
3739

@@ -42,6 +44,8 @@ public class AndroidDisplayService implements DisplayService {
4244

4345
private static final Logger LOG = Logger.getLogger(AndroidDisplayService.class.getName());
4446

47+
private static final ReadOnlyObjectWrapper<Insets> insetsProperty = new ReadOnlyObjectWrapper<>();
48+
4549
private static final boolean debug = Util.DEBUG;
4650

4751
static {
@@ -118,8 +122,18 @@ public ReadOnlyObjectProperty<Notch> notchProperty() {
118122
return new ReadOnlyObjectWrapper<>(Notch.UNKNOWN).getReadOnlyProperty();
119123
}
120124

125+
@Override
126+
public ReadOnlyObjectProperty<Insets> systemBarsInsetsProperty() {
127+
return insetsProperty.getReadOnlyProperty();
128+
}
129+
121130
// native
122131
private native static boolean isPhoneFactor();
123132
private native static double[] screenSize();
124133
private native static boolean screenRound();
134+
135+
// callback
136+
private static void notifyInsets(double top, double right, double bottom, double left) {
137+
Platform.runLater(() -> insetsProperty.set(new Insets(top, right, bottom, left)));
138+
}
125139
}

modules/display/src/main/java/com/gluonhq/attach/display/impl/DesktopDisplayService.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2019 Gluon
2+
* Copyright (c) 2016, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -31,6 +31,7 @@
3131
import javafx.beans.property.ReadOnlyObjectProperty;
3232
import javafx.beans.property.ReadOnlyObjectWrapper;
3333
import javafx.geometry.Dimension2D;
34+
import javafx.geometry.Insets;
3435
import javafx.geometry.Rectangle2D;
3536
import javafx.stage.Screen;
3637

@@ -41,6 +42,8 @@ public class DesktopDisplayService implements DisplayService {
4142

4243
private static final Logger LOG = Logger.getLogger(DesktopDisplayService.class.getName());
4344

45+
private static final ReadOnlyObjectWrapper<Insets> insetsProperty = new ReadOnlyObjectWrapper<>();
46+
4447
private final Dimension2D dimensions;
4548

4649
public DesktopDisplayService() {
@@ -101,6 +104,11 @@ public ReadOnlyObjectProperty<Notch> notchProperty() {
101104
return new ReadOnlyObjectWrapper<>(Notch.UNKNOWN).getReadOnlyProperty();
102105
}
103106

107+
@Override
108+
public ReadOnlyObjectProperty<Insets> systemBarsInsetsProperty() {
109+
return insetsProperty.getReadOnlyProperty();
110+
}
111+
104112
private static void log(String message, Throwable cause) {
105113
LOG.log(Level.FINE, message);
106114
if (LOG.isLoggable(Level.FINE)) {

modules/display/src/main/java/com/gluonhq/attach/display/impl/IOSDisplayService.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2019 Gluon
2+
* Copyright (c) 2016, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@
3434
import javafx.beans.property.ReadOnlyObjectProperty;
3535
import javafx.beans.property.ReadOnlyObjectWrapper;
3636
import javafx.geometry.Dimension2D;
37+
import javafx.geometry.Insets;
3738

3839
public class IOSDisplayService implements DisplayService {
3940

@@ -44,6 +45,8 @@ public class IOSDisplayService implements DisplayService {
4445

4546
private static ReadOnlyObjectWrapper<DisplayService.Notch> notch;
4647

48+
private static final ReadOnlyObjectWrapper<Insets> insetsProperty = new ReadOnlyObjectWrapper<>();
49+
4750
public IOSDisplayService() {
4851
notch = new ReadOnlyObjectWrapper<>(Notch.UNKNOWN);
4952
LifecycleService.create().ifPresent(l -> {
@@ -100,6 +103,11 @@ public ReadOnlyObjectProperty<Notch> notchProperty() {
100103
return notch.getReadOnlyProperty();
101104
}
102105

106+
@Override
107+
public ReadOnlyObjectProperty<Insets> systemBarsInsetsProperty() {
108+
return insetsProperty.getReadOnlyProperty();
109+
}
110+
103111
// native
104112
private static native void initDisplay();
105113

@@ -119,4 +127,8 @@ private static void notifyDisplay(String o) {
119127
Platform.runLater(() -> notch.setValue(d));
120128
}
121129
}
130+
131+
private static void notifyInsets(double top, double right, double bottom, double left) {
132+
Platform.runLater(() -> insetsProperty.set(new Insets(top, right, bottom, left)));
133+
}
122134
}

modules/display/src/main/native/android/c/display.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Gluon
2+
* Copyright (c) 2020, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -34,6 +34,10 @@ static jmethodID jDisplayServiceHeightMethod;
3434
static jmethodID jDisplayServiceFactorMethod;
3535
static jmethodID jDisplayServiceRoundMethod;
3636

37+
// Graal handles
38+
static jclass jGraalDisplayClass;
39+
static jmethodID jGraalNotifyInsetsMethod;
40+
3741
static void initializeDisplayDalvikHandles() {
3842
jDisplayServiceClass = GET_REGISTER_DALVIK_CLASS(jDisplayServiceClass, "com/gluonhq/helloandroid/DalvikDisplayService");
3943
ATTACH_DALVIK();
@@ -49,6 +53,11 @@ static void initializeDisplayDalvikHandles() {
4953
DETACH_DALVIK();
5054
}
5155

56+
static void initializeGraalHandles(JNIEnv* env) {
57+
jGraalDisplayClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/display/impl/AndroidDisplayService"));
58+
jGraalNotifyInsetsMethod = (*env)->GetStaticMethodID(env, jGraalDisplayClass, "notifyInsets", "(DDDD)V");
59+
}
60+
5261
//////////////////////////
5362
// From Graal to native //
5463
//////////////////////////
@@ -65,6 +74,7 @@ JNI_OnLoad_display(JavaVM *vm, void *reserved)
6574
return JNI_FALSE;
6675
}
6776
ATTACH_LOG_FINE("[Display Service] Initializing native Display from OnLoad");
77+
initializeGraalHandles(graalEnv);
6878
initializeDisplayDalvikHandles();
6979
return JNI_VERSION_1_8;
7080
#else
@@ -109,3 +119,17 @@ JNIEXPORT jboolean JNICALL Java_com_gluonhq_attach_display_impl_AndroidDisplaySe
109119
DETACH_DALVIK();
110120
return answer;
111121
}
122+
123+
///////////////////////////
124+
// From Dalvik to native //
125+
///////////////////////////
126+
127+
JNIEXPORT void JNICALL Java_com_gluonhq_helloandroid_DalvikDisplayService_notifyInsets(
128+
JNIEnv *env, jobject service, jdouble top, jdouble right, jdouble bottom, jdouble left) {
129+
if (isDebugAttach()) {
130+
ATTACH_LOG_FINE("Native layer got new inset: %.1f,%.1f,%.1f,%.1f\n", top, right, bottom, left);
131+
}
132+
ATTACH_GRAAL();
133+
(*graalEnv)->CallStaticVoidMethod(graalEnv, jGraalDisplayClass, jGraalNotifyInsetsMethod, top, right, bottom, left);
134+
DETACH_GRAAL();
135+
}

modules/display/src/main/native/android/dalvik/DalvikDisplayService.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, Gluon
2+
* Copyright (c) 2020, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -33,8 +33,14 @@
3333
import android.util.DisplayMetrics;
3434
import android.util.Log;
3535
import android.view.Display;
36+
import android.view.View;
37+
import android.view.Window;
3638
import android.view.WindowManager;
3739

40+
import androidx.core.graphics.Insets;
41+
import androidx.core.view.ViewCompat;
42+
import androidx.core.view.WindowInsetsCompat;
43+
3844
public class DalvikDisplayService {
3945

4046
private static final String TAG = Util.TAG;
@@ -53,6 +59,16 @@ public DalvikDisplayService(Activity activity) {
5359
float yInches = metrics.heightPixels / metrics.ydpi;
5460
float xInches = metrics.widthPixels / metrics.xdpi;
5561
diagonalInches = Math.sqrt(xInches * xInches + yInches * yInches);
62+
63+
Window window = activity.getWindow();
64+
View decorView = window.getDecorView();
65+
ViewCompat.setOnApplyWindowInsetsListener(decorView, (v, insets) -> {
66+
notifyInsets(insets, metrics.density);
67+
return insets;
68+
});
69+
// Get initial insets
70+
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
71+
notifyInsets(insets, metrics.density);
5672
}
5773

5874
private boolean isPhoneFactor() {
@@ -73,4 +89,15 @@ private boolean isScreenRound() {
7389
}
7490
return activity.getResources().getConfiguration().isScreenRound();
7591
}
92+
93+
private void notifyInsets(WindowInsetsCompat insets, double density) {
94+
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
95+
if (Util.isDebug()) {
96+
Log.v(TAG, "Display got new insets: " + systemBars);
97+
}
98+
notifyInsets(systemBars.top / density, systemBars.right / density,
99+
systemBars.bottom / density, systemBars.left / density);
100+
}
101+
102+
private native void notifyInsets(double top, double right, double bottom, double left);
76103
}

modules/display/src/main/native/ios/Display.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022 Gluon
2+
* Copyright (c) 2018, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -36,6 +36,8 @@
3636
- (void) startObserver;
3737
- (void) stopObserver;
3838
- (NSString*) getNotch;
39+
- (UIEdgeInsets) getInsets;
3940
@end
4041

41-
void sendNotch();
42+
void sendNotch();
43+
void sendInsets(UIEdgeInsets insets);

modules/display/src/main/native/ios/Display.m

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2023, Gluon
2+
* Copyright (c) 2016, 2025, Gluon
33
*
44
* This program is free software: you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License as published by
@@ -48,6 +48,7 @@
4848
// Display
4949
jclass mat_jDisplayServiceClass;
5050
jmethodID mat_jDisplayService_notifyDisplay = 0;
51+
jmethodID mat_jDisplayService_notifyInsets = 0;
5152
Display *_display;
5253

5354
bool iPhoneX;
@@ -63,6 +64,7 @@
6364

6465
mat_jDisplayServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/display/impl/IOSDisplayService"));
6566
mat_jDisplayService_notifyDisplay = (*env)->GetStaticMethodID(env, mat_jDisplayServiceClass, "notifyDisplay", "(Ljava/lang/String;)V");
67+
mat_jDisplayService_notifyInsets = (*env)->GetStaticMethodID(env, mat_jDisplayServiceClass, "notifyInsets", "(DDDD)V");
6668

6769
_display = [[Display alloc] init];
6870
[_display isIPhoneX];
@@ -155,6 +157,13 @@ void sendNotch() {
155157
(*env)->DeleteLocalRef(env, arg);
156158
}
157159

160+
void sendInsets(UIEdgeInsets insets) {
161+
if (debugAttach) {
162+
AttachLog(@"Insets are %.3f %.3f %.3f %.3f", insets.top, insets.right, insets.bottom, insets.left);
163+
}
164+
(*env)->CallStaticVoidMethod(env, mat_jDisplayServiceClass, mat_jDisplayService_notifyInsets, insets.top, insets.right, insets.bottom, insets.left);
165+
}
166+
158167
@implementation Display
159168

160169
NSString * GetDeviceModel(void)
@@ -190,7 +199,8 @@ - (void) isIPhoneX
190199
@"iPhone14,2", @"iPhone14,3", @"iPhone14,4", @"iPhone14,5", // iPhone 13 Pro, 13 Pro Max, 13 Mini, 13
191200
@"iPhone14,7", @"iPhone14,8", @"iPhone15,2", @"iPhone15,3", // iPhone 14, 14 Plus, 14 Pro, 14 Pro Max
192201
@"iPhone15,4", @"iPhone15,5", @"iPhone16,1", @"iPhone16,2", // iPhone 15, 15 Plus, 15 Pro, 15 Pro Max
193-
@"iPhone17,3", @"iPhone17,4", @"iPhone17,1", @"iPhone17,2", // iPhone 16, 16 Plus, 16 Pro, 16 Pro Max
202+
@"iPhone17,3", @"iPhone17,4", @"iPhone17,1", @"iPhone17,2", @"iPhone17,5", // iPhone 16, 16 Plus, 16 Pro, 16 Pro Max, 16e
203+
@"iPhone18,3", @"iPhone18,1", @"iPhone18,2", @"iPhone18,4", // iPhone 17, 17 Pro, 17 Pro Max, 17 Pro Air
194204
];
195205

196206
if ([modelsWithNotch containsObject:GetDeviceModel()]) {
@@ -234,19 +244,17 @@ - (void) isIPhoneX
234244

235245
- (void) startObserver
236246
{
247+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
248+
sendInsets([self getInsets]);
237249
if (iPhoneX)
238250
{
239-
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
240251
sendNotch();
241252
}
242253
}
243254

244255
- (void) stopObserver
245256
{
246-
if (iPhoneX)
247-
{
248-
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
249-
}
257+
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
250258
}
251259

252260
- (NSString*) getNotch
@@ -271,9 +279,26 @@ - (NSString*) getNotch
271279
return value;
272280
}
273281

282+
- (UIEdgeInsets) getInsets
283+
{
284+
if (@available(iOS 11.0, *)) {
285+
UIWindow *window = UIApplication.sharedApplication.keyWindow;
286+
if (!window) {
287+
AttachLog(@"key window was nil");
288+
return UIEdgeInsetsMake(0, 0, 0, 0);
289+
}
290+
return window.safeAreaInsets;
291+
}
292+
return UIEdgeInsetsMake(0, 0, 0, 0);
293+
}
294+
274295
-(void)OrientationDidChange:(NSNotification*)notification
275296
{
276-
sendNotch();
297+
sendInsets([self getInsets]);
298+
if (iPhoneX)
299+
{
300+
sendNotch();
301+
}
277302
}
278303

279304
@end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
{
3+
"name" : "com.gluonhq.attach.display.impl.AndroidDisplayService",
4+
"methods":[{"name":"notifyInsets","parameterTypes":["double", "double", "double", "double"] }]
5+
}
6+
]

0 commit comments

Comments
 (0)