Skip to content

Commit a690449

Browse files
committed
Main.openBrowser(): try to use XDG Desktop Portal OpenURI on Linux if the AWT-based method is not available / fails
1 parent 5470192 commit a690449

File tree

3 files changed

+124
-8
lines changed

3 files changed

+124
-8
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* Copyright (C) 2025 Matteo Hausner
2+
*
3+
* This program is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
*/
16+
17+
package de.bwravencl.controllerbuddy.dbus.freedesktop;
18+
19+
import java.util.Map;
20+
import org.freedesktop.dbus.DBusPath;
21+
import org.freedesktop.dbus.annotations.DBusInterfaceName;
22+
import org.freedesktop.dbus.interfaces.DBusInterface;
23+
import org.freedesktop.dbus.types.Variant;
24+
25+
@DBusInterfaceName("org.freedesktop.portal.OpenURI")
26+
public interface OpenURI extends DBusInterface {
27+
28+
@SuppressWarnings("MethodNameSameAsClassName")
29+
DBusPath OpenURI(String parentWindow, String uri, Map<String, Variant<?>> options);
30+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* Copyright (C) 2025 Matteo Hausner
2+
*
3+
* This program is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
*/
16+
17+
package de.bwravencl.controllerbuddy.dbus.freedesktop;
18+
19+
import java.util.Map;
20+
import org.freedesktop.dbus.annotations.DBusInterfaceName;
21+
import org.freedesktop.dbus.exceptions.DBusException;
22+
import org.freedesktop.dbus.interfaces.DBusInterface;
23+
import org.freedesktop.dbus.messages.DBusSignal;
24+
import org.freedesktop.dbus.types.UInt32;
25+
import org.freedesktop.dbus.types.Variant;
26+
27+
@SuppressWarnings("unused")
28+
@DBusInterfaceName("org.freedesktop.portal.Request")
29+
public interface Request extends DBusInterface {
30+
31+
class Response extends DBusSignal {
32+
33+
private final UInt32 response;
34+
35+
public Response(final String path, final UInt32 response, final Map<String, Variant<?>> results)
36+
throws DBusException {
37+
super(path, response, results);
38+
39+
this.response = response;
40+
}
41+
42+
public UInt32 getResponse() {
43+
return response;
44+
}
45+
}
46+
}

src/main/java/de/bwravencl/controllerbuddy/gui/Main.java

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.sun.jna.Platform;
2626
import com.sun.jna.platform.unix.X11;
2727
import de.bwravencl.controllerbuddy.constants.Constants;
28+
import de.bwravencl.controllerbuddy.dbus.freedesktop.OpenURI;
29+
import de.bwravencl.controllerbuddy.dbus.freedesktop.Request;
2830
import de.bwravencl.controllerbuddy.dbus.gnome.Extensions;
2931
import de.bwravencl.controllerbuddy.gui.GuiUtils.FrameDragListener;
3032
import de.bwravencl.controllerbuddy.input.Input;
@@ -135,6 +137,7 @@
135137
import java.util.Timer;
136138
import java.util.TimerTask;
137139
import java.util.concurrent.Callable;
140+
import java.util.concurrent.CompletableFuture;
138141
import java.util.concurrent.Executors;
139142
import java.util.concurrent.ScheduledExecutorService;
140143
import java.util.concurrent.TimeUnit;
@@ -218,6 +221,7 @@
218221
import org.apache.commons.cli.HelpFormatter;
219222
import org.apache.commons.cli.Options;
220223
import org.apache.commons.cli.ParseException;
224+
import org.freedesktop.dbus.DBusMatchRule;
221225
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
222226
import org.lwjgl.glfw.GLFW;
223227
import org.lwjgl.system.Configuration;
@@ -1467,16 +1471,46 @@ public static void main(final String[] args) {
14671471
}
14681472

14691473
private static void openBrowser(final Component parentComponent, final URI uri) {
1470-
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
1471-
try {
1472-
Desktop.getDesktop().browse(uri);
1473-
} catch (final IOException e) {
1474-
throw new RuntimeException(e);
1474+
if (Desktop.isDesktopSupported()) {
1475+
final var desktop = Desktop.getDesktop();
1476+
1477+
if (desktop.isSupported(Desktop.Action.BROWSE)) {
1478+
try {
1479+
desktop.browse(uri);
1480+
return;
1481+
} catch (final Throwable t) {
1482+
log.log(Level.WARNING, t.getMessage(), t);
1483+
}
14751484
}
1485+
}
1486+
1487+
if (isLinux) {
1488+
new Thread(() -> {
1489+
var success = false;
1490+
1491+
try (final var dBusConnection = DBusConnectionBuilder.forSessionBus().build()) {
1492+
final var openUriInterface = dBusConnection.getRemoteObject("org.freedesktop.portal.Desktop",
1493+
"/org/freedesktop/portal/desktop", OpenURI.class);
1494+
1495+
final var responseCompletableFuture = new CompletableFuture<Request.Response>();
1496+
1497+
try (final var _ = dBusConnection.addSigHandler(new DBusMatchRule(Request.Response.class),
1498+
responseCompletableFuture::complete)) {
1499+
final var requestPath = openUriInterface.OpenURI("", uri.toString(), Collections.emptyMap())
1500+
.getPath();
1501+
final var response = responseCompletableFuture.get(3, TimeUnit.SECONDS);
1502+
success = response.getPath().equals(requestPath) && response.getResponse().intValue() == 0;
1503+
}
1504+
} catch (final Throwable t) {
1505+
log.log(Level.WARNING, t.getMessage(), t);
1506+
}
1507+
1508+
if (!success) {
1509+
EventQueue.invokeLater(() -> showPleaseVisitDialog(parentComponent, uri));
1510+
}
1511+
}).start();
14761512
} else {
1477-
JOptionPane.showMessageDialog(parentComponent,
1478-
MessageFormat.format(strings.getString("PLEASE_VISIT_DIALOG_TEXT"), uri),
1479-
strings.getString("INFORMATION_DIALOG_TITLE"), JOptionPane.INFORMATION_MESSAGE);
1513+
showPleaseVisitDialog(parentComponent, uri);
14801514
}
14811515
}
14821516

@@ -1496,6 +1530,12 @@ private static void printCommandLineMessage(final String message) {
14961530
}
14971531
}
14981532

1533+
private static void showPleaseVisitDialog(final Component parentComponent, final URI uri) {
1534+
JOptionPane.showMessageDialog(parentComponent,
1535+
MessageFormat.format(strings.getString("PLEASE_VISIT_DIALOG_TEXT"), uri),
1536+
strings.getString("INFORMATION_DIALOG_TITLE"), JOptionPane.INFORMATION_MESSAGE);
1537+
}
1538+
14991539
private static void terminate(final int status, final Main main) {
15001540
if (main != null && main.taskRunner != null) {
15011541
main.taskRunner.shutdown();

0 commit comments

Comments
 (0)