diff --git a/pom.xml b/pom.xml
index ae541c4b..9bb2fc09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,7 @@
1.4.11
2.0.9
2.0
+ 4.3.1
@@ -196,6 +197,18 @@
${xtaudio.version}
+
+ com.github.hypfvieh
+ dbus-java-core
+ ${dbus.java.version}
+
+
+
+ com.github.hypfvieh
+ dbus-java-transport-junixsocket
+ ${dbus.java.version}
+
+
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 9b2b9027..feefb6e1 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -16,6 +16,7 @@
requires java.net.http;
requires org.slf4j;
requires ch.qos.logback.classic;
+ requires org.freedesktop.dbus;
opens org.dpsoftware to javafx.fxml, javafx.web;
opens org.dpsoftware.gui to javafx.fxml, javafx.web;
diff --git a/src/main/java/org/dpsoftware/FireflyLuciferin.java b/src/main/java/org/dpsoftware/FireflyLuciferin.java
index 473e127a..d03e8b2c 100644
--- a/src/main/java/org/dpsoftware/FireflyLuciferin.java
+++ b/src/main/java/org/dpsoftware/FireflyLuciferin.java
@@ -272,7 +272,7 @@ public void start(Stage stage) throws Exception {
if (MainSingleton.getInstance().config.getRuntimeLogLevel().equals("DEBUG")) {
MainSingleton.getInstance().guiManager.showSettingsDialog(true);
}
- NativeExecutor.isWayland();
+// NativeExecutor.isWayland();
}
/**
@@ -285,6 +285,7 @@ private void launchGrabberAndConsumers() throws AWTException {
// Desktop Duplication API producers
if ((MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.DDUPL.name()))
|| (MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.XIMAGESRC.name()))
+ || (MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.PIPEWIREXDG.name()))
|| (MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.AVFVIDEOSRC.name()))) {
grabberManager.launchAdvancedGrabber(imageProcessor);
} else { // Standard Producers
diff --git a/src/main/java/org/dpsoftware/config/Configuration.java b/src/main/java/org/dpsoftware/config/Configuration.java
index ee9b52a9..55680a70 100644
--- a/src/main/java/org/dpsoftware/config/Configuration.java
+++ b/src/main/java/org/dpsoftware/config/Configuration.java
@@ -189,6 +189,7 @@ public enum CaptureMethod {
WinAPI,
DDUPL,
XIMAGESRC,
+ PIPEWIREXDG,
AVFVIDEOSRC
}
diff --git a/src/main/java/org/dpsoftware/config/Constants.java b/src/main/java/org/dpsoftware/config/Constants.java
index 62592d26..10f28198 100644
--- a/src/main/java/org/dpsoftware/config/Constants.java
+++ b/src/main/java/org/dpsoftware/config/Constants.java
@@ -495,6 +495,7 @@ public class Constants {
public static final String GSTREAMER_PIPELINE_WINDOWS_HARDWARE_HANDLE_SM = "d3d11screencapturesrc monitor-handle={0} ! d3d11convert ! d3d11download";
public static final String GSTREAMER_PIPELINE_WINDOWS_HARDWARE_HANDLE = "d3d11screencapturesrc monitor-handle={0} ! d3d11convert";
public static final String GSTREAMER_PIPELINE_LINUX = "ximagesrc startx={0} endx={1} starty={2} endy={3} use-damage=0 ! videoscale ! videoconvert";
+ public static final String GSTREAMER_PIPELINE_PIPEWIREXDG = "pipewiresrc fd={1} path={2} ! videorate ! videoscale ! videoconvert";
public static final String GSTREAMER_PIPELINE_MAC = "avfvideosrc capture-screen=true ! videoscale ! videoconvert";
public static final String FRAMERATE_PLACEHOLDER = "framerate=FRAMERATE_PLACEHOLDER/1,";
public static final String FPS_PLACEHOLDER = "FRAMERATE_PLACEHOLDER";
diff --git a/src/main/java/org/dpsoftware/grabber/DbusScreenCast.java b/src/main/java/org/dpsoftware/grabber/DbusScreenCast.java
new file mode 100644
index 00000000..427e78fa
--- /dev/null
+++ b/src/main/java/org/dpsoftware/grabber/DbusScreenCast.java
@@ -0,0 +1,29 @@
+package org.dpsoftware.grabber;
+
+import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.FileDescriptor;
+import org.freedesktop.dbus.annotations.DBusInterfaceName;
+import org.freedesktop.dbus.annotations.DBusProperty;
+import org.freedesktop.dbus.annotations.DBusProperty.Access;
+import org.freedesktop.dbus.interfaces.DBusInterface;
+import org.freedesktop.dbus.types.UInt32;
+import org.freedesktop.dbus.types.Variant;
+
+import java.util.Map;
+
+/**
+ * Auto-generated class.
+ */
+@DBusProperty(name = "AvailableSourceTypes", type = UInt32.class, access = Access.READ)
+@DBusProperty(name = "AvailableCursorModes", type = UInt32.class, access = Access.READ)
+@DBusProperty(name = "version", type = UInt32.class, access = Access.READ)
+@DBusInterfaceName(value = "org.freedesktop.portal.ScreenCast")
+public interface DbusScreenCast extends DBusInterface {
+
+
+ public DBusPath CreateSession(Map> options);
+ public DBusPath SelectSources(DBusPath sessionHandle, Map> options);
+ public DBusPath Start(DBusPath sessionHandle, String parentWindow, Map> options);
+ public FileDescriptor OpenPipeWireRemote(DBusPath sessionHandle, Map> options);
+
+}
diff --git a/src/main/java/org/dpsoftware/gui/controllers/MiscTabController.java b/src/main/java/org/dpsoftware/gui/controllers/MiscTabController.java
index 152e5e2c..a4c07a45 100644
--- a/src/main/java/org/dpsoftware/gui/controllers/MiscTabController.java
+++ b/src/main/java/org/dpsoftware/gui/controllers/MiscTabController.java
@@ -301,7 +301,8 @@ public void initValuesFromSettingsFile(Configuration currentConfig, boolean upda
}
frameInsertion.setDisable((!currentConfig.getCaptureMethod().equals(Configuration.CaptureMethod.DDUPL.name()))
&& (!currentConfig.getCaptureMethod().equals(Configuration.CaptureMethod.XIMAGESRC.name()))
- && (!currentConfig.getCaptureMethod().equals(Configuration.CaptureMethod.AVFVIDEOSRC.name())));
+ && (!currentConfig.getCaptureMethod().equals(Configuration.CaptureMethod.PIPEWIREXDG.name())
+ && (!currentConfig.getCaptureMethod().equals(Configuration.CaptureMethod.AVFVIDEOSRC.name()))));
gamma.setValue(String.valueOf(MainSingleton.getInstance().config.getGamma()));
colorMode.setValue(Enums.ColorMode.values()[MainSingleton.getInstance().config.getColorMode() - 1].getI18n());
if (!MainSingleton.getInstance().config.getDesiredFramerate().equals(Enums.Framerate.UNLOCKED.getBaseI18n())) {
diff --git a/src/main/java/org/dpsoftware/gui/controllers/ModeTabController.java b/src/main/java/org/dpsoftware/gui/controllers/ModeTabController.java
index 5be054e0..c8ed34d5 100644
--- a/src/main/java/org/dpsoftware/gui/controllers/ModeTabController.java
+++ b/src/main/java/org/dpsoftware/gui/controllers/ModeTabController.java
@@ -94,7 +94,7 @@ public void injectSettingsController(SettingsController settingsController) {
@FXML
protected void initialize() {
if (NativeExecutor.isLinux()) {
- captureMethod.getItems().addAll(Configuration.CaptureMethod.XIMAGESRC);
+ captureMethod.getItems().addAll(Configuration.CaptureMethod.XIMAGESRC, Configuration.CaptureMethod.PIPEWIREXDG);
}
for (Enums.Algo al : Enums.Algo.values()) {
algo.getItems().add(al.getI18n());
@@ -158,7 +158,11 @@ void initDefaultValues() {
} else if (NativeExecutor.isMac()) {
captureMethod.setValue(Configuration.CaptureMethod.DDUPL);
} else {
- captureMethod.setValue(Configuration.CaptureMethod.XIMAGESRC);
+ if (System.getenv("XDG_SESSION_TYPE").equalsIgnoreCase("wayland")) {
+ captureMethod.setValue(Configuration.CaptureMethod.PIPEWIREXDG);
+ } else {
+ captureMethod.setValue(Configuration.CaptureMethod.XIMAGESRC);
+ }
}
}
}
diff --git a/src/main/java/org/dpsoftware/gui/controllers/SettingsController.java b/src/main/java/org/dpsoftware/gui/controllers/SettingsController.java
index 4aa0cd30..816f470c 100644
--- a/src/main/java/org/dpsoftware/gui/controllers/SettingsController.java
+++ b/src/main/java/org/dpsoftware/gui/controllers/SettingsController.java
@@ -526,6 +526,8 @@ void setCaptureMethod(Configuration config) {
} else {
if (modeTabController.captureMethod.getValue() == Configuration.CaptureMethod.XIMAGESRC) {
config.setCaptureMethod(Configuration.CaptureMethod.XIMAGESRC.name());
+ } else if (modeTabController.captureMethod.getValue() == Configuration.CaptureMethod.PIPEWIREXDG) {
+ config.setCaptureMethod(Configuration.CaptureMethod.PIPEWIREXDG.name());
}
}
}
@@ -753,6 +755,8 @@ public void initOutputDeviceChooser(boolean initCaptureMethod) {
modeTabController.captureMethod.getItems().addAll(Configuration.CaptureMethod.DDUPL, Configuration.CaptureMethod.WinAPI, Configuration.CaptureMethod.CPU);
} else if (NativeExecutor.isMac()) {
modeTabController.captureMethod.getItems().addAll(Configuration.CaptureMethod.AVFVIDEOSRC);
+ } else {
+ modeTabController.captureMethod.getItems().addAll(Configuration.CaptureMethod.XIMAGESRC, Configuration.CaptureMethod.PIPEWIREXDG);
}
}
if (NativeExecutor.isWindows()) {
diff --git a/src/main/java/org/dpsoftware/managers/PipelineManager.java b/src/main/java/org/dpsoftware/managers/PipelineManager.java
index e4954b7b..45ceef45 100644
--- a/src/main/java/org/dpsoftware/managers/PipelineManager.java
+++ b/src/main/java/org/dpsoftware/managers/PipelineManager.java
@@ -21,6 +21,7 @@
*/
package org.dpsoftware.managers;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dpsoftware.MainSingleton;
import org.dpsoftware.audio.*;
@@ -28,6 +29,7 @@
import org.dpsoftware.config.Constants;
import org.dpsoftware.config.Enums;
import org.dpsoftware.config.LocalizedEnum;
+import org.dpsoftware.grabber.DbusScreenCast;
import org.dpsoftware.grabber.GrabberSingleton;
import org.dpsoftware.gui.GuiSingleton;
import org.dpsoftware.gui.elements.DisplayInfo;
@@ -40,10 +42,22 @@
import org.dpsoftware.network.MessageClient;
import org.dpsoftware.network.NetworkSingleton;
import org.dpsoftware.utilities.CommonUtility;
+import org.freedesktop.dbus.DBusMap;
+import org.freedesktop.dbus.DBusMatchRule;
+import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.FileDescriptor;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.types.UInt32;
+import org.freedesktop.dbus.types.Variant;
import java.awt.*;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -58,21 +72,90 @@ public class PipelineManager {
UpgradeManager upgradeManager = new UpgradeManager();
private ScheduledExecutorService scheduledExecutorService;
+
+ record XdgStreamDetails(Integer streamId, FileDescriptor fileDescriptor) {}
+ /**
+ * Uses D-BUS to get the XDG ScreenCast stream ID & pipewire filedescriptor
+ *
+ * @throws RuntimeException on any concurrency or D-BUS issues
+ * @return XDG ScreenCast stream details containing the ID from org.freedesktop.portal.ScreenCast:Start and
+ * FileDescriptor from org.freedesktop.portal.ScreenCast:OpenPipeWireRemote
+ */
+ @SneakyThrows
+ public static XdgStreamDetails getXdgStreamDetails() {
+ CompletableFuture sessionHandleMaybe = new CompletableFuture<>();
+ CompletableFuture streamIdMaybe = new CompletableFuture<>();
+
+ DBusConnection dBusConnection = DBusConnectionBuilder.forSessionBus().build(); // cannot free/close this for the duration of the capture
+
+ DbusScreenCast screenCastIface = dBusConnection.getRemoteObject("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", DbusScreenCast.class);
+ String handleToken = UUID.randomUUID().toString().replaceAll("-", "");
+
+ DBusMatchRule matchRule = new DBusMatchRule("signal", "org.freedesktop.portal.Request", "Response");
+ dBusConnection.addGenericSigHandler(matchRule, signal -> {
+ try {
+ if (signal.getParameters().length == 2 // verify amount of arguments
+ && signal.getParameters()[0] instanceof UInt32 // verify argument types
+ && signal.getParameters()[1] instanceof DBusMap
+ && ((UInt32)signal.getParameters()[0]).intValue() == 0 // verify success-code
+ ) {
+ // parse signal & set appropriate Future as the result
+ if (((DBusMap, ?>) signal.getParameters()[1]).containsKey("session_handle")) {
+ sessionHandleMaybe.complete((String)(((Variant>)((DBusMap, ?>) signal.getParameters()[1]).get("session_handle")).getValue()));
+ } else if (((DBusMap, ?>) signal.getParameters()[1]).containsKey("streams")) {
+ streamIdMaybe.complete(((UInt32)((Object[]) ((List>) (((Variant>) ((DBusMap, ?>) signal.getParameters()[1]).get("streams")).getValue())).get(0))[0]).intValue());
+ }
+ }
+ } catch (DBusException e) {
+ throw new RuntimeException(e); // couldn't parse, fail early
+ }
+ });
+
+ screenCastIface.CreateSession(Map.of("session_handle_token", new Variant<>(handleToken)));
+ DBusPath receivedSessionHandle = new DBusPath(sessionHandleMaybe.get());
+
+ screenCastIface.SelectSources(receivedSessionHandle,
+ Map.of(
+ "multiple", new Variant<>(false),
+ "types", new Variant<>(new UInt32(1|2)) // bitmask, 1 - screens, 2 - windows
+ )
+ );
+ screenCastIface.Start(receivedSessionHandle, "", Collections.emptyMap());
+
+ return streamIdMaybe.thenApply(streamId -> {
+ FileDescriptor fileDescriptor = screenCastIface.OpenPipeWireRemote(receivedSessionHandle, Collections.emptyMap()); // block until stream started before calling OpenPipeWireRemote
+
+ return new XdgStreamDetails(streamId, fileDescriptor);
+ }).get();
+ }
+
/**
* Calculate correct Pipeline for Linux
*
* @return params for Linux Pipeline
*/
public static String getLinuxPipelineParams() {
- // startx{0}, endx{1}, starty{2}, endy{3}
- DisplayManager displayManager = new DisplayManager();
- List displayList = displayManager.getDisplayList();
- DisplayInfo monitorInfo = displayList.get(MainSingleton.getInstance().config.getMonitorNumber());
- String gstreamerPipeline = Constants.GSTREAMER_PIPELINE_LINUX
- .replace("{0}", String.valueOf((int) (monitorInfo.getMinX() + 1)))
- .replace("{1}", String.valueOf((int) (monitorInfo.getMinX() + monitorInfo.getWidth() - 1)))
- .replace("{2}", String.valueOf((int) (monitorInfo.getMinY())))
- .replace("{3}", String.valueOf((int) (monitorInfo.getMinY() + monitorInfo.getHeight() - 1)));
+ String gstreamerPipeline;
+
+ if (MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.PIPEWIREXDG.name())) {
+ XdgStreamDetails xdgStreamDetails = getXdgStreamDetails();
+
+ gstreamerPipeline = Constants.GSTREAMER_PIPELINE_PIPEWIREXDG
+ .replace("{1}", String.valueOf(xdgStreamDetails.fileDescriptor.getIntFileDescriptor()))
+ .replace("{2}", xdgStreamDetails.streamId.toString());
+ } else {
+ // startx{0}, endx{1}, starty{2}, endy{3}
+ DisplayManager displayManager = new DisplayManager();
+ List displayList = displayManager.getDisplayList();
+ DisplayInfo monitorInfo = displayList.get(MainSingleton.getInstance().config.getMonitorNumber());
+
+ gstreamerPipeline = Constants.GSTREAMER_PIPELINE_LINUX
+ .replace("{0}", String.valueOf((int) (monitorInfo.getMinX() + 1)))
+ .replace("{1}", String.valueOf((int) (monitorInfo.getMinX() + monitorInfo.getWidth() - 1)))
+ .replace("{2}", String.valueOf((int) (monitorInfo.getMinY())))
+ .replace("{3}", String.valueOf((int) (monitorInfo.getMinY() + monitorInfo.getHeight() - 1)));
+ }
+
log.info(gstreamerPipeline);
return gstreamerPipeline;
}
@@ -332,6 +415,7 @@ public void stopCapturePipeline() {
}
if (GrabberSingleton.getInstance().pipe != null && ((MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.DDUPL.name()))
|| (MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.XIMAGESRC.name()))
+ || (MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.PIPEWIREXDG.name()))
|| (MainSingleton.getInstance().config.getCaptureMethod().equals(Configuration.CaptureMethod.AVFVIDEOSRC.name())))) {
GrabberSingleton.getInstance().pipe.stop();
}