Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wayland support #148

Merged
merged 5 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<logback.version>1.4.11</logback.version>
<slf4j.version>2.0.9</slf4j.version>
<xtaudio.version>2.0</xtaudio.version>
<dbus.java.version>4.3.1</dbus.java.version>
</properties>

<profiles>
Expand Down Expand Up @@ -196,6 +197,18 @@
<version>${xtaudio.version}</version>
</dependency>

<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>dbus-java-core</artifactId>
<version>${dbus.java.version}</version>
</dependency>

<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>dbus-java-transport-junixsocket</artifactId>
<version>${dbus.java.version}</version>
</dependency>

</dependencies>

<build>
Expand Down
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/dpsoftware/FireflyLuciferin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/dpsoftware/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public enum CaptureMethod {
WinAPI,
DDUPL,
XIMAGESRC,
PIPEWIREXDG,
AVFVIDEOSRC
}

Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/dpsoftware/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/org/dpsoftware/grabber/DbusScreenCast.java
Original file line number Diff line number Diff line change
@@ -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<String, Variant<?>> options);
public DBusPath SelectSources(DBusPath sessionHandle, Map<String, Variant<?>> options);
public DBusPath Start(DBusPath sessionHandle, String parentWindow, Map<String, Variant<?>> options);
public FileDescriptor OpenPipeWireRemote(DBusPath sessionHandle, Map<String, Variant<?>> options);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Expand Down Expand Up @@ -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()) {
Expand Down
102 changes: 93 additions & 9 deletions src/main/java/org/dpsoftware/managers/PipelineManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
*/
package org.dpsoftware.managers;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dpsoftware.MainSingleton;
import org.dpsoftware.audio.*;
import org.dpsoftware.config.Configuration;
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;
Expand All @@ -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;
Expand All @@ -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<String> sessionHandleMaybe = new CompletableFuture<>();
CompletableFuture<Integer> 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<DisplayInfo> 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<DisplayInfo> 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;
}
Expand Down Expand Up @@ -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();
}
Expand Down