summaryrefslogtreecommitdiffstats
path: root/src/detection/displayserver/linux/wayland/wayland.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/displayserver/linux/wayland/wayland.c')
-rw-r--r--src/detection/displayserver/linux/wayland/wayland.c340
1 files changed, 340 insertions, 0 deletions
diff --git a/src/detection/displayserver/linux/wayland/wayland.c b/src/detection/displayserver/linux/wayland/wayland.c
new file mode 100644
index 0000000..064c239
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/wayland.c
@@ -0,0 +1,340 @@
+#include "../displayserver_linux.h"
+#include "common/io.h"
+#include "common/edidHelper.h"
+#include "common/strutil.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef FF_HAVE_WAYLAND
+
+ #include <sys/socket.h>
+
+ #include "common/properties.h"
+
+ #include "wayland.h"
+ #include "wlr-output-management-unstable-v1-client-protocol.h"
+ #include "kde-output-device-v2-client-protocol.h"
+ #include "kde-output-order-v1-client-protocol.h"
+ #include "xdg-output-unstable-v1-client-protocol.h"
+
+ #if __FreeBSD__
+ #include <sys/un.h>
+ #include <sys/ucred.h>
+ #include <sys/sysctl.h>
+ #endif
+
+static bool waylandDetectWM(int fd, FFDisplayServerResult* result) {
+ #if __linux__ || __GNU__ || (__FreeBSD__ && !__DragonFly__)
+
+ #if __linux__ || __GNU__
+ struct ucred ucred = {};
+ socklen_t len = sizeof(ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1 || ucred.pid <= 0) {
+ return false;
+ }
+
+ FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreate();
+ ffStrbufAppendF(&procPath, "/proc/%d/cmdline", ucred.pid); // We check the cmdline for the process name, because it is not trimmed.
+ if (!ffReadFileBuffer(procPath.chars, &result->wmProcessName)) {
+ return false;
+ }
+ #else
+ struct xucred ucred = {};
+ socklen_t len = sizeof(ucred);
+ if (getsockopt(fd, AF_UNSPEC, LOCAL_PEERCRED, &ucred, &len) == -1 || ucred.cr_pid <= 0) {
+ return false;
+ }
+
+ size_t size = 4096;
+ ffStrbufEnsureFixedLengthFree(&result->wmProcessName, (uint32_t) size);
+
+ if (sysctl((int[]) { CTL_KERN, KERN_PROC, KERN_PROC_ARGS, ucred.cr_pid }, 4, result->wmProcessName.chars, &size, NULL, 0) != 0) {
+ return false;
+ }
+ result->wmProcessName.length = (uint32_t) size - 1;
+ #endif
+
+ // #1135: wl-restart is a special case
+ const char* filename = strrchr(result->wmProcessName.chars, '/');
+ if (filename) {
+ filename++;
+ } else {
+ filename = result->wmProcessName.chars;
+ }
+
+ if (ffStrEquals(filename, "wl-restart")) {
+ ffStrbufSubstrAfterLastC(&result->wmProcessName, '\0');
+ }
+
+ ffStrbufSubstrBeforeFirstC(&result->wmProcessName, '\0'); // Trim the arguments
+ ffStrbufSubstrAfterLastC(&result->wmProcessName, '/'); // Trim the path
+
+ return true;
+
+ #else
+ FF_UNUSED(fd, result);
+ return false;
+ #endif
+}
+
+static void waylandGlobalAddListener(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
+ WaylandData* wldata = data;
+
+ if ((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_GLOBAL) && ffStrEquals(interface, wldata->ffwl_output_interface->name)) {
+ wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_GLOBAL;
+ if (ffWaylandHandleGlobalOutput(wldata, registry, name, version) != NULL) {
+ wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;
+ }
+ } else if ((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_ZWLR) && ffStrEquals(interface, zwlr_output_manager_v1_interface.name)) {
+ wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_ZWLR;
+ if (ffWaylandHandleZwlrOutput(wldata, registry, name, version) != NULL) {
+ wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;
+ }
+ } else if ((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_KDE) && ffStrEquals(interface, kde_output_device_v2_interface.name)) {
+ wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_KDE;
+ if (ffWaylandHandleKdeOutput(wldata, registry, name, version) != NULL) {
+ wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;
+ }
+ } else if (ffStrEquals(interface, kde_output_order_v1_interface.name)) {
+ ffWaylandHandleKdeOutputOrder(wldata, registry, name, version);
+ } else if ((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_GLOBAL || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE) && ffStrEquals(interface, zxdg_output_manager_v1_interface.name)) {
+ ffWaylandHandleZxdgOutput(wldata, registry, name, version);
+ }
+}
+
+static FF_A_UNUSED bool matchDrmConnector(const char* connName, WaylandDisplay* wldata) {
+ // https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output-event-name
+ // The doc says that "do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc."
+ // However I can't find a better method to get the edid data
+ const char* drmDirPath = "/sys/class/drm/";
+
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);
+ if (dirp == NULL) {
+ return false;
+ }
+
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ const char* plainName = entry->d_name;
+ if (ffStrStartsWith(plainName, "card")) {
+ const char* tmp = strchr(plainName + strlen("card"), '-');
+ if (tmp) {
+ plainName = tmp + 1;
+ }
+ }
+ if (ffStrEquals(plainName, connName)) {
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateF("%s%s/edid", drmDirPath, entry->d_name);
+
+ uint8_t edidData[512];
+ ssize_t edidLength = ffReadFileData(path.chars, ARRAY_SIZE(edidData), edidData);
+ if (edidLength > 0 && edidLength % 128 == 0) {
+ ffEdidGetName(edidData, &wldata->edidName);
+ ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength);
+ ffEdidGetSerialAndManufactureDate(edidData, &wldata->serial, &wldata->myear, &wldata->mweek);
+ wldata->hdrInfoAvailable = true;
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+}
+
+void ffWaylandOutputNameListener(void* data, FF_A_UNUSED void* output, const char* name) {
+ WaylandDisplay* display = data;
+ if (display->id) {
+ return;
+ }
+
+ display->type = ffdsGetDisplayType(name);
+ #if __linux__
+ if (!display->edidName.length) {
+ matchDrmConnector(name, display);
+ }
+ #endif
+ display->id = ffWaylandGenerateIdFromName(name);
+ ffStrbufAppendS(&display->name, name);
+}
+
+void ffWaylandOutputDescriptionListener(void* data, FF_A_UNUSED void* output, const char* description) {
+ WaylandDisplay* display = data;
+ if (display->description.length) {
+ return;
+ }
+
+ while (*description == ' ') {
+ ++description;
+ }
+ if (!ffStrEquals(description, "Unknown Display") && !ffStrContains(description, "(null)")) {
+ ffStrbufAppendS(&display->description, description);
+ }
+}
+
+uint32_t ffWaylandHandleRotation(WaylandDisplay* display) {
+ uint32_t rotation;
+ switch (display->transform) {
+ case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+ case WL_OUTPUT_TRANSFORM_90:
+ rotation = 90;
+ break;
+ case WL_OUTPUT_TRANSFORM_FLIPPED_180:
+ case WL_OUTPUT_TRANSFORM_180:
+ rotation = 180;
+ break;
+ case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+ case WL_OUTPUT_TRANSFORM_270:
+ rotation = 270;
+ break;
+ default:
+ rotation = 0;
+ break;
+ }
+
+ switch (rotation) {
+ case 90:
+ case 270: {
+ int32_t temp = display->width;
+ display->width = display->height;
+ display->height = temp;
+
+ temp = display->physicalWidth;
+ display->physicalWidth = display->physicalHeight;
+ display->physicalHeight = temp;
+ break;
+ }
+ default:
+ break;
+ }
+ return rotation;
+}
+
+const char* ffdsConnectWayland(FFDisplayServerResult* result) {
+ if (getenv("XDG_RUNTIME_DIR") == NULL) {
+ return "Wayland requires $XDG_RUNTIME_DIR being set";
+ }
+
+ FF_LIBRARY_LOAD_MESSAGE(wayland, "libwayland-client" FF_LIBRARY_EXTENSION, 1)
+
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_connect)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_get_fd)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_proxy_marshal_constructor)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_disconnect)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_registry_interface)
+
+ WaylandData data = {};
+
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_marshal_constructor_versioned)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_add_listener)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_destroy)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_display_roundtrip)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_output_interface)
+
+ data.display = ffwl_display_connect(NULL);
+ if (data.display == NULL) {
+ return "wl_display_connect returned NULL";
+ }
+
+ waylandDetectWM(ffwl_display_get_fd(data.display), result);
+
+ struct wl_proxy* registry = ffwl_proxy_marshal_constructor((struct wl_proxy*) data.display, WL_DISPLAY_GET_REGISTRY, ffwl_registry_interface, NULL);
+ if (registry == NULL) {
+ ffwl_display_disconnect(data.display);
+ return "wl_display_get_registry returned NULL";
+ }
+
+ data.result = result;
+
+ struct wl_registry_listener registry_listener = {
+ .global = waylandGlobalAddListener,
+ .global_remove = (void*) stubListener
+ };
+
+ data.ffwl_proxy_add_listener(registry, (void (**)(void)) &registry_listener, &data);
+ data.ffwl_display_roundtrip(data.display);
+
+ if (data.zxdgOutputManager) {
+ data.ffwl_proxy_destroy(data.zxdgOutputManager);
+ }
+
+ data.ffwl_proxy_destroy(registry);
+ ffwl_display_disconnect(data.display);
+
+ if (data.primaryDisplayId == 0 && result->wmProcessName.length > 0) {
+ const char* fileName = ffStrbufEqualS(&result->wmProcessName, "gnome-shell")
+ ? "monitors.xml"
+ : ffStrbufEqualS(&result->wmProcessName, "cinnamon")
+ ? "cinnamon-monitors.xml"
+ : NULL;
+ if (fileName) {
+ FF_STRBUF_AUTO_DESTROY monitorsXml = ffStrbufCreate();
+ FF_LIST_FOR_EACH (FFstrbuf, basePath, instance.state.platform.configDirs) {
+ char path[1024];
+ snprintf(path, ARRAY_SIZE(path), "%s%s", basePath->chars, fileName);
+ if (ffReadFileBuffer(path, &monitorsXml)) {
+ break;
+ }
+ }
+ if (monitorsXml.length) {
+ // <monitors version="2">
+ // <configuration>
+ // <logicalmonitor>
+ // <x>0</x>
+ // <y>0</y>
+ // <scale>1.7489879131317139</scale>
+ // <primary>yes</primary>
+ // <monitor>
+ // <monitorspec>
+ // <connector>Virtual-1</connector>
+ // <vendor>unknown</vendor>
+ // <product>unknown</product>
+ // <serial>unknown</serial>
+ // </monitorspec>
+ // <mode>
+ // <width>3456</width>
+ // <height>2160</height>
+ // <rate>60.000068664550781</rate>
+ // </mode>
+ // </monitor>
+ // </logicalmonitor>
+ // </configuration>
+ // </monitors>
+ uint32_t start = ffStrbufFirstIndexS(&monitorsXml, "<primary>yes</primary>");
+ if (start < monitorsXml.length) {
+ start = ffStrbufNextIndexS(&monitorsXml, start, "<connector>");
+ if (start < monitorsXml.length) {
+ uint32_t end = ffStrbufNextIndexS(&monitorsXml, start, "</connector>");
+ if (end < monitorsXml.length) {
+ ffStrbufSubstrBefore(&monitorsXml, end);
+ const char* name = monitorsXml.chars + start + strlen("<connector>");
+ data.primaryDisplayId = ffWaylandGenerateIdFromName(name);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (data.primaryDisplayId) {
+ FF_LIST_FOR_EACH (FFDisplayResult, d, data.result->displays) {
+ if (d->id == data.primaryDisplayId) {
+ d->primary = true;
+ break;
+ }
+ }
+ }
+
+ // We successfully connected to wayland and detected the display.
+ // So we can set set the session type to wayland.
+ // This is used as an indicator that we are running wayland by the x11 backends.
+ ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND);
+ return NULL;
+}
+
+#else
+
+const char* ffdsConnectWayland(FF_A_UNUSED FFDisplayServerResult* result) {
+ return "Fastfetch was compiled without Wayland support";
+}
+
+#endif