summaryrefslogtreecommitdiffstats
path: root/src/detection/displayserver/linux
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/displayserver/linux
Add the files
Diffstat (limited to 'src/detection/displayserver/linux')
-rw-r--r--src/detection/displayserver/linux/common.c16
-rw-r--r--src/detection/displayserver/linux/displayserver_linux.c91
-rw-r--r--src/detection/displayserver/linux/displayserver_linux.h13
-rw-r--r--src/detection/displayserver/linux/drm.c437
-rw-r--r--src/detection/displayserver/linux/wayland/global-output.c173
-rw-r--r--src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h1384
-rw-r--r--src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c128
-rw-r--r--src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h134
-rw-r--r--src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c37
-rw-r--r--src/detection/displayserver/linux/wayland/kde-output.c300
-rw-r--r--src/detection/displayserver/linux/wayland/wayland.c340
-rw-r--r--src/detection/displayserver/linux/wayland/wayland.h89
-rw-r--r--src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h1292
-rw-r--r--src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c176
-rw-r--r--src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h414
-rw-r--r--src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c76
-rw-r--r--src/detection/displayserver/linux/wayland/zwlr-output.c212
-rw-r--r--src/detection/displayserver/linux/wmde.c523
-rw-r--r--src/detection/displayserver/linux/xcb.c441
-rw-r--r--src/detection/displayserver/linux/xlib.c365
20 files changed, 6641 insertions, 0 deletions
diff --git a/src/detection/displayserver/linux/common.c b/src/detection/displayserver/linux/common.c
new file mode 100644
index 0000000..305766d
--- /dev/null
+++ b/src/detection/displayserver/linux/common.c
@@ -0,0 +1,16 @@
+#include "displayserver_linux.h"
+#include "common/strutil.h"
+
+FFDisplayType ffdsGetDisplayType(const char* name) {
+ if (ffStrStartsWith(name, "eDP-") || ffStrStartsWith(name, "LVDS-")) {
+ return FF_DISPLAY_TYPE_BUILTIN;
+ } else if (ffStrStartsWith(name, "HDMI-") ||
+ ffStrStartsWith(name, "DP-") ||
+ ffStrStartsWith(name, "DisplayPort-") ||
+ ffStrStartsWith(name, "DVI-") ||
+ ffStrStartsWith(name, "VGA-")) {
+ return FF_DISPLAY_TYPE_EXTERNAL;
+ }
+
+ return FF_DISPLAY_TYPE_UNKNOWN;
+}
diff --git a/src/detection/displayserver/linux/displayserver_linux.c b/src/detection/displayserver/linux/displayserver_linux.c
new file mode 100644
index 0000000..70d2d40
--- /dev/null
+++ b/src/detection/displayserver/linux/displayserver_linux.c
@@ -0,0 +1,91 @@
+#include "displayserver_linux.h"
+#include "common/io.h"
+#include "common/strutil.h"
+
+#ifdef __FreeBSD__
+ #include "common/settings.h"
+#endif
+
+static void getWMProtocolNameFromEnv(FFDisplayServerResult* result) {
+ const char* env = getenv("XDG_SESSION_TYPE");
+ if (env) {
+ if (ffStrEqualsIgnCase(env, "wayland")) {
+ ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND);
+ } else if (ffStrEqualsIgnCase(env, "x11") || ffStrEqualsIgnCase(env, "xorg")) {
+ ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);
+ } else if (ffStrEqualsIgnCase(env, "tty")) {
+ ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_TTY);
+ } else {
+ ffStrbufSetS(&result->wmProtocolName, env);
+ }
+
+ return;
+ }
+
+ if (getenv("WAYLAND_DISPLAY") != NULL || getenv("WAYLAND_SOCKET") != NULL) {
+ ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND);
+ return;
+ }
+
+ if (getenv("DISPLAY") != NULL) // XWayland also set this
+ {
+ ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_X11);
+ return;
+ }
+
+ env = getenv("TERM");
+ if (ffStrSet(env) && ffStrEqualsIgnCase(env, "linux")) {
+ ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_TTY);
+ return;
+ }
+}
+
+void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
+ if (instance.config.general.dsForceDrm == FF_DS_FORCE_DRM_TYPE_FALSE) {
+ // We try wayland as our preferred display server, as it supports the most features.
+ // This method can't detect the name of our WM / DE
+ ffdsConnectWayland(ds);
+
+ // Try the x11 libs, from most feature rich to least.
+ // We use the display list to detect if a connection is needed.
+ // They respect wmProtocolName, and only detect display if it is set.
+ if (ds->displays.length == 0) {
+ ffdsConnectXcbRandr(ds);
+ }
+
+ if (ds->displays.length == 0) {
+ ffdsConnectXrandr(ds);
+ }
+ }
+
+ // This display detection method is display server independent.
+ // Use it if all connections failed
+ if (ds->displays.length == 0) {
+ ffdsConnectDrm(ds);
+ }
+
+#ifdef __FreeBSD__
+ if (ds->displays.length == 0) {
+ FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
+ if (ffSettingsGetFreeBSDKenv("screen.width", &buf)) {
+ uint32_t width = (uint32_t) ffStrbufToUInt(&buf, 0);
+ if (width) {
+ ffStrbufClear(&buf);
+ if (ffSettingsGetFreeBSDKenv("screen.height", &buf)) {
+ uint32_t height = (uint32_t) ffStrbufToUInt(&buf, 0);
+ ffdsAppendDisplay(ds, width, height, 0, 0, 0, 0, 0, 0, NULL, FF_DISPLAY_TYPE_UNKNOWN, false, 0, 0, 0, "kenv");
+ }
+ }
+ }
+ }
+#endif
+
+ if (ds->wmProtocolName.length == 0) {
+ getWMProtocolNameFromEnv(ds);
+ }
+
+ if (!ffStrbufEqualS(&ds->wmProtocolName, FF_WM_PROTOCOL_TTY)) {
+ // This fills in missing information about WM / DE by using env vars and iterating processes
+ ffdsDetectWMDE(ds);
+ }
+}
diff --git a/src/detection/displayserver/linux/displayserver_linux.h b/src/detection/displayserver/linux/displayserver_linux.h
new file mode 100644
index 0000000..19ae4e7
--- /dev/null
+++ b/src/detection/displayserver/linux/displayserver_linux.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "detection/displayserver/displayserver.h"
+
+const char* ffdsConnectWayland(FFDisplayServerResult* result);
+
+const char* ffdsConnectXcbRandr(FFDisplayServerResult* result);
+const char* ffdsConnectXrandr(FFDisplayServerResult* result);
+const char* ffdsConnectDrm(FFDisplayServerResult* result);
+
+void ffdsDetectWMDE(FFDisplayServerResult* result);
+
+FFDisplayType ffdsGetDisplayType(const char* drmConnectorName);
diff --git a/src/detection/displayserver/linux/drm.c b/src/detection/displayserver/linux/drm.c
new file mode 100644
index 0000000..43c6afc
--- /dev/null
+++ b/src/detection/displayserver/linux/drm.c
@@ -0,0 +1,437 @@
+#include "displayserver_linux.h"
+#include "common/io.h"
+#include "common/edidHelper.h"
+#include "common/strutil.h"
+
+#ifdef __linux__
+ #include <dirent.h>
+
+static const char* drmParseSysfs(FFDisplayServerResult* result) {
+ const char* drmDirPath = "/sys/class/drm/";
+
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);
+ if (dirp == NULL) {
+ return "opendir(drmDirPath) failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);
+ ffStrbufAppendS(&drmDir, drmDirPath);
+
+ uint32_t drmDirLength = drmDir.length;
+
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+
+ ffStrbufAppendS(&drmDir, entry->d_name);
+ uint32_t drmDirWithDnameLength = drmDir.length;
+
+ char buf;
+ ffStrbufAppendS(&drmDir, "/enabled");
+ if (ffReadFileData(drmDir.chars, sizeof(buf), &buf) <= 0 || buf != 'e') {
+ /* read failed or enabled != "enabled" */
+ ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
+ ffStrbufAppendS(&drmDir, "/status");
+ buf = 'd';
+ ffReadFileData(drmDir.chars, sizeof(buf), &buf);
+ if (buf != 'c') {
+ /* read failed or status != "connected" */
+ ffStrbufSubstrBefore(&drmDir, drmDirLength);
+ continue;
+ }
+ }
+
+ unsigned width = 0, height = 0, physicalWidth = 0, physicalHeight = 0;
+ double refreshRate = 0;
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
+
+ ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
+ ffStrbufAppendS(&drmDir, "/edid");
+
+ const char* plainName = entry->d_name;
+ if (ffStrStartsWith(plainName, "card")) {
+ const char* tmp = strchr(plainName + strlen("card"), '-');
+ if (tmp) {
+ plainName = tmp + 1;
+ }
+ }
+
+ uint8_t edidData[512];
+ ssize_t edidLength = ffReadFileData(drmDir.chars, ARRAY_SIZE(edidData), edidData);
+ if (edidLength <= 0 || edidLength % 128 != 0) {
+ edidLength = 0;
+ ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
+ ffStrbufAppendS(&drmDir, "/modes");
+
+ char modes[32];
+ if (ffReadFileData(drmDir.chars, ARRAY_SIZE(modes), modes) >= 3) {
+ sscanf(modes, "%ux%u", &width, &height);
+ ffStrbufAppendS(&name, plainName);
+ }
+ } else {
+ ffEdidGetName(edidData, &name);
+ ffEdidGetPreferredResolutionAndRefreshRate(edidData, &width, &height, &refreshRate);
+ ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight);
+ }
+
+ FFDisplayResult* item = ffdsAppendDisplay(
+ result,
+ width,
+ height,
+ refreshRate,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ &name,
+ ffdsGetDisplayType(plainName),
+ false,
+ 0,
+ physicalWidth,
+ physicalHeight,
+ "sysfs-drm");
+ if (item && edidLength) {
+ item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek);
+ }
+
+ ffStrbufSubstrBefore(&drmDir, drmDirLength);
+ }
+
+ return NULL;
+}
+#endif
+
+#ifdef FF_HAVE_DRM
+
+ #include "common/library.h"
+
+ #include <xf86drm.h>
+ #include <xf86drmMode.h>
+ #include <fcntl.h>
+
+// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c#L1785
+// It's not supported on Ubuntu 20.04
+static inline const char* drmType2Name(uint32_t connector_type) {
+ /* Keep the strings in sync with the kernel's drm_connector_enum_list in
+ * drm_connector.c. */
+ switch (connector_type) {
+ case DRM_MODE_CONNECTOR_Unknown:
+ return "Unknown";
+ case DRM_MODE_CONNECTOR_VGA:
+ return "VGA";
+ case DRM_MODE_CONNECTOR_DVII:
+ return "DVI-I";
+ case DRM_MODE_CONNECTOR_DVID:
+ return "DVI-D";
+ case DRM_MODE_CONNECTOR_DVIA:
+ return "DVI-A";
+ case DRM_MODE_CONNECTOR_Composite:
+ return "Composite";
+ case DRM_MODE_CONNECTOR_SVIDEO:
+ return "SVIDEO";
+ case DRM_MODE_CONNECTOR_LVDS:
+ return "LVDS";
+ case DRM_MODE_CONNECTOR_Component:
+ return "Component";
+ case DRM_MODE_CONNECTOR_9PinDIN:
+ return "DIN";
+ case DRM_MODE_CONNECTOR_DisplayPort:
+ return "DP";
+ case DRM_MODE_CONNECTOR_HDMIA:
+ return "HDMI-A";
+ case DRM_MODE_CONNECTOR_HDMIB:
+ return "HDMI-B";
+ case DRM_MODE_CONNECTOR_TV:
+ return "TV";
+ case DRM_MODE_CONNECTOR_eDP:
+ return "eDP";
+ case DRM_MODE_CONNECTOR_VIRTUAL:
+ return "Virtual";
+ case DRM_MODE_CONNECTOR_DSI:
+ return "DSI";
+ case DRM_MODE_CONNECTOR_DPI:
+ return "DPI";
+ case DRM_MODE_CONNECTOR_WRITEBACK:
+ return "Writeback";
+ case 19 /*DRM_MODE_CONNECTOR_SPI*/:
+ return "SPI";
+ case 20 /*DRM_MODE_CONNECTOR_USB*/:
+ return "USB";
+ default:
+ return "Unsupported";
+ }
+}
+
+FF_A_UNUSED static const char* drmGetEdidByConnId(uint32_t connId, uint8_t* edidData, ssize_t* edidLength) {
+ const char* drmDirPath = "/sys/class/drm/";
+
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath);
+ if (dirp == NULL) {
+ return "opendir(drmDirPath) failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);
+ ffStrbufAppendS(&drmDir, drmDirPath);
+
+ uint32_t drmDirLength = drmDir.length;
+
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+
+ ffStrbufAppendS(&drmDir, entry->d_name);
+ uint32_t drmDirWithDnameLength = drmDir.length;
+
+ char connectorId[16] = {};
+
+ ffStrbufAppendS(&drmDir, "/connector_id");
+ ffReadFileData(drmDir.chars, ARRAY_SIZE(connectorId), connectorId);
+ if (strtoul(connectorId, NULL, 10) != connId) {
+ ffStrbufSubstrBefore(&drmDir, drmDirLength);
+ continue;
+ }
+
+ ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength);
+ ffStrbufAppendS(&drmDir, "/edid");
+ *edidLength = ffReadFileData(drmDir.chars, (uint32_t) *edidLength, edidData);
+ return NULL;
+ }
+
+ return "Failed to match connector ID";
+}
+
+static const char* drmConnectLibdrm(FFDisplayServerResult* result) {
+ FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm" FF_LIBRARY_EXTENSION, 2)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetResources)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetConnectorCurrent)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetCrtc)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetEncoder)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetFB)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetProperty)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetPropertyBlob)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeResources)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeCrtc)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeConnector)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeEncoder)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeFB)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeProperty)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreePropertyBlob)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices)
+
+ drmDevice* devices[64];
+ int nDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices));
+ if (nDevices <= 0) {
+ return "drmGetDevices() failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
+
+ for (int iDev = 0; iDev < nDevices; ++iDev) {
+ drmDevice* dev = devices[iDev];
+
+ if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) {
+ continue;
+ }
+
+ const char* path = dev->nodes[DRM_NODE_PRIMARY];
+
+ #if __linux__
+ ffStrbufSetF(&name, "/sys/class/drm/%s/device/power/runtime_status", strrchr(path, '/') + 1);
+
+ char buffer[8] = "";
+ if (ffReadFileData(name.chars, strlen("suspend"), buffer) > 0 && ffStrStartsWith(buffer, "suspend")) {
+ continue;
+ }
+ #endif
+
+ FF_AUTO_CLOSE_FD int primaryFd = open(path, O_RDWR | O_CLOEXEC);
+ if (primaryFd < 0) {
+ continue;
+ }
+
+ drmModeRes* res = ffdrmModeGetResources(primaryFd);
+ if (!res) {
+ continue;
+ }
+
+ for (int iConn = 0; iConn < res->count_connectors; ++iConn) {
+ drmModeConnector* conn = ffdrmModeGetConnectorCurrent(primaryFd, res->connectors[iConn]);
+ if (!conn) {
+ continue;
+ }
+
+ if (conn->connection != DRM_MODE_DISCONNECTED) {
+ drmModeEncoder* encoder = ffdrmModeGetEncoder(primaryFd, conn->encoder_id);
+ uint32_t width = 0, height = 0, refreshRate = 0;
+ uint8_t bitDepth = 0;
+
+ if (encoder) {
+ drmModeCrtc* crtc = ffdrmModeGetCrtc(primaryFd, encoder->crtc_id);
+ if (crtc) {
+ width = crtc->mode.hdisplay;
+ height = crtc->mode.vdisplay;
+ refreshRate = crtc->mode.vrefresh;
+ if (refreshRate == 0) {
+ // There are weird cases that we can't get the refresh rate from the CRTC but from the modes
+ for (int iMode = 0; iMode < conn->count_modes; ++iMode) {
+ drmModeModeInfo* mode = &conn->modes[iMode];
+ if (mode->clock == crtc->mode.clock && mode->htotal == crtc->mode.htotal) {
+ refreshRate = mode->vrefresh;
+ break;
+ }
+ }
+ }
+
+ drmModeFBPtr fb = ffdrmModeGetFB(primaryFd, crtc->buffer_id);
+ if (fb) {
+ bitDepth = (uint8_t) (fb->depth / 3);
+ ffdrmModeFreeFB(fb);
+ }
+
+ ffdrmModeFreeCrtc(crtc);
+ }
+
+ ffdrmModeFreeEncoder(encoder);
+ }
+
+ uint32_t preferredWidth = 0, preferredHeight = 0, preferredRefreshRate = 0;
+
+ for (int iMode = 0; iMode < conn->count_modes; ++iMode) {
+ drmModeModeInfo* mode = &conn->modes[iMode];
+
+ if (mode->type & DRM_MODE_TYPE_PREFERRED) {
+ preferredWidth = mode->hdisplay;
+ preferredHeight = mode->vdisplay;
+ preferredRefreshRate = mode->vrefresh;
+ break;
+ }
+ }
+
+ // NVIDIA DRM driver seems incomplete and conn->encoder_id == 0
+ // Assume preferred resolution is used as what we do in drmParseSys
+ if (width == 0 || height == 0) {
+ width = preferredWidth;
+ height = preferredHeight;
+ refreshRate = preferredRefreshRate;
+ }
+
+ ffStrbufClear(&name);
+ uint16_t myear = 0, mweak = 0;
+ uint32_t serial = 0;
+ FFDisplayHdrStatus hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
+
+ for (int iProp = 0; iProp < conn->count_props; ++iProp) {
+ drmModePropertyRes* prop = ffdrmModeGetProperty(primaryFd, conn->props[iProp]);
+ if (!prop) {
+ continue;
+ }
+
+ uint32_t type = prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE);
+ if (type == DRM_MODE_PROP_BLOB && ffStrEquals(prop->name, "EDID")) {
+ drmModePropertyBlobPtr blob = NULL;
+
+ if (prop->count_blobs > 0 && prop->blob_ids != NULL) {
+ blob = ffdrmModeGetPropertyBlob(primaryFd, prop->blob_ids[0]);
+ } else {
+ blob = ffdrmModeGetPropertyBlob(primaryFd, (uint32_t) conn->prop_values[iProp]);
+ }
+
+ if (blob) {
+ if (blob->length >= 128) {
+ ffEdidGetName(blob->data, &name);
+ hdrStatus = ffEdidGetHdrCompatible(blob->data, blob->length) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ ffEdidGetSerialAndManufactureDate(blob->data, &serial, &myear, &mweak);
+ }
+ ffdrmModeFreePropertyBlob(blob);
+ }
+ break;
+ }
+ ffdrmModeFreeProperty(prop);
+ }
+
+ #if __linux__
+ if (name.length == 0) {
+ uint8_t edidData[512];
+ ssize_t edidLength = 0;
+ drmGetEdidByConnId(conn->connector_id, edidData, &edidLength);
+ if (edidLength > 0 && edidLength % 128 == 0) {
+ ffEdidGetName(edidData, &name);
+ hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ ffEdidGetSerialAndManufactureDate(edidData, &serial, &myear, &mweak);
+ }
+ }
+ #endif
+
+ if (name.length == 0) {
+ const char* connectorTypeName = drmType2Name(conn->connector_type);
+ if (connectorTypeName == NULL) {
+ connectorTypeName = "Unknown";
+ }
+ ffStrbufSetF(&name, "%s-%d", connectorTypeName, iConn + 1);
+ }
+
+ FFDisplayResult* item = ffdsAppendDisplay(result,
+ width,
+ height,
+ refreshRate,
+ 0,
+ preferredWidth,
+ preferredHeight,
+ preferredRefreshRate,
+ 0,
+ &name,
+ conn->connector_type == DRM_MODE_CONNECTOR_eDP || conn->connector_type == DRM_MODE_CONNECTOR_LVDS
+ ? FF_DISPLAY_TYPE_BUILTIN
+ : conn->connector_type == DRM_MODE_CONNECTOR_HDMIA || conn->connector_type == DRM_MODE_CONNECTOR_HDMIB || conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort
+ ? FF_DISPLAY_TYPE_EXTERNAL
+ : FF_DISPLAY_TYPE_UNKNOWN,
+ false,
+ conn->connector_id,
+ conn->mmWidth,
+ conn->mmHeight,
+ "libdrm");
+
+ if (item) {
+ item->hdrStatus = hdrStatus;
+ item->serial = serial;
+ item->manufactureYear = myear;
+ item->manufactureWeek = mweak;
+ item->bitDepth = bitDepth;
+ }
+ }
+
+ ffdrmModeFreeConnector(conn);
+ }
+
+ ffdrmModeFreeResources(res);
+ }
+
+ ffdrmFreeDevices(devices, nDevices);
+
+ return NULL;
+}
+
+#endif
+
+const char* ffdsConnectDrm(FF_A_UNUSED FFDisplayServerResult* result) {
+#ifdef FF_HAVE_DRM
+ if (instance.config.general.dsForceDrm != FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY) {
+ if (drmConnectLibdrm(result) == NULL) {
+ return NULL;
+ }
+ }
+#endif
+
+#ifdef __linux__
+ return drmParseSysfs(result);
+#endif
+
+ return "fastfetch was compiled without drm support";
+}
diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c
new file mode 100644
index 0000000..51fb425
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/global-output.c
@@ -0,0 +1,173 @@
+#ifdef FF_HAVE_WAYLAND
+
+ #include "wayland.h"
+ #include "common/strutil.h"
+ #include "xdg-output-unstable-v1-client-protocol.h"
+
+static void waylandOutputModeListener(void* data, FF_A_UNUSED struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refreshRate) {
+ WaylandDisplay* display = data;
+
+ if (flags & WL_OUTPUT_MODE_CURRENT) {
+ display->width = width;
+ display->height = height;
+ display->refreshRate = refreshRate;
+ }
+ if (flags & WL_OUTPUT_MODE_PREFERRED) {
+ display->preferredWidth = width;
+ display->preferredHeight = height;
+ display->preferredRefreshRate = refreshRate;
+ }
+}
+
+static void waylandOutputScaleListener(void* data, FF_A_UNUSED struct wl_output* output, int32_t scale) {
+ WaylandDisplay* display = data;
+ display->dpi = 96 * (uint32_t) scale;
+}
+
+static void waylandOutputGeometryListener(void* data,
+ FF_A_UNUSED struct wl_output* output,
+ FF_A_UNUSED int32_t x,
+ FF_A_UNUSED int32_t y,
+ int32_t physical_width,
+ int32_t physical_height,
+ FF_A_UNUSED int32_t subpixel,
+ FF_A_UNUSED const char* make,
+ FF_A_UNUSED const char* model,
+ int32_t transform) {
+ WaylandDisplay* display = data;
+ display->physicalWidth = physical_width;
+ display->physicalHeight = physical_height;
+ display->transform = (enum wl_output_transform) transform;
+}
+
+static void handleXdgLogicalSize(void* data, FF_A_UNUSED struct zxdg_output_v1* _, int32_t width, FF_A_UNUSED int32_t height) {
+ WaylandDisplay* display = data;
+ // Seems the values are only useful when ractional scale is enabled
+ if (width < display->width) {
+ display->dpi = (uint32_t) (display->width * 96 / width);
+ }
+}
+
+// Dirty hack for #477
+// The order of these callbacks MUST follow `struct wl_output_listener`
+static void* outputListener[] = {
+ waylandOutputGeometryListener, // geometry
+ waylandOutputModeListener, // mode
+ stubListener, // done
+ waylandOutputScaleListener, // scale
+ ffWaylandOutputNameListener, // name
+ ffWaylandOutputDescriptionListener, // description
+};
+static_assert(
+ sizeof(outputListener) >= sizeof(struct wl_output_listener),
+ "sizeof(outputListener) is too small. Please report it to fastfetch github issue");
+
+static struct zxdg_output_v1_listener zxdgOutputListener = {
+ .logical_position = (void*) stubListener,
+ .logical_size = handleXdgLogicalSize,
+ .done = (void*) stubListener,
+ .name = (void*) ffWaylandOutputNameListener,
+ .description = (void*) ffWaylandOutputDescriptionListener,
+};
+
+const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
+ const char* api = "wayland-global";
+ uint32_t bindVersion = min(version, WL_OUTPUT_DESCRIPTION_SINCE_VERSION);
+ struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, wldata->ffwl_output_interface, bindVersion, name, wldata->ffwl_output_interface->name, bindVersion, NULL);
+ if (output == NULL) {
+ return "Failed to create wl_output";
+ }
+
+ WaylandDisplay display = {
+ .parent = wldata,
+ .transform = WL_OUTPUT_TRANSFORM_NORMAL,
+ .type = FF_DISPLAY_TYPE_UNKNOWN,
+ .name = ffStrbufCreate(),
+ .description = ffStrbufCreate(),
+ .edidName = ffStrbufCreate(),
+ };
+
+ if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, &display) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to add listener to wl_output";
+ }
+ if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to roundtrip wl_output";
+ }
+
+ if (wldata->zxdgOutputManager) {
+ uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION);
+ struct wl_proxy* zxdgOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->zxdgOutputManager, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, bindVersion, NULL, output);
+
+ if (zxdgOutput) {
+ wldata->ffwl_proxy_add_listener(zxdgOutput, (void (**)(void)) &zxdgOutputListener, &display);
+ wldata->ffwl_display_roundtrip(wldata->display);
+ wldata->ffwl_proxy_destroy(zxdgOutput);
+ api = "wayland-global-zxdg";
+ }
+ }
+
+ wldata->ffwl_proxy_destroy(output);
+
+ if (display.width <= 0 || display.height <= 0) {
+ return "Failed to get display information from wl_output";
+ }
+
+ uint32_t rotation = ffWaylandHandleRotation(&display);
+
+ FFDisplayResult* item = ffdsAppendDisplay(wldata->result,
+ (uint32_t) display.width,
+ (uint32_t) display.height,
+ display.refreshRate / 1000.0,
+ display.dpi,
+ (uint32_t) display.preferredWidth,
+ (uint32_t) display.preferredHeight,
+ display.preferredRefreshRate / 1000.0,
+ rotation,
+ display.edidName.length
+ ? &display.edidName
+ // Try ignoring `eDP-1-unknown`, where `unknown` is localized
+ : display.description.length && !ffStrbufContain(&display.description, &display.name)
+ ? &display.description
+ : &display.name,
+ display.type,
+ false,
+ display.id,
+ (uint32_t) display.physicalWidth,
+ (uint32_t) display.physicalHeight,
+ api);
+ if (item) {
+ if (display.hdrSupported) {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
+ } else if (display.hdrInfoAvailable) {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ } else {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
+ }
+
+ item->manufactureYear = display.myear;
+ item->manufactureWeek = display.mweek;
+ item->serial = display.serial;
+ }
+
+ ffStrbufDestroy(&display.description);
+ ffStrbufDestroy(&display.name);
+ ffStrbufDestroy(&display.edidName);
+
+ return NULL;
+}
+
+const char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
+ uint32_t bindVersion = min(version, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION);
+ struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zxdg_output_manager_v1_interface, bindVersion, name, zxdg_output_manager_v1_interface.name, bindVersion, NULL);
+ if (manager == NULL) {
+ return "Failed to create zxdg_output_manager_v1";
+ }
+
+ wldata->zxdgOutputManager = manager;
+
+ return NULL;
+}
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h b/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h
new file mode 100644
index 0000000..44ab8ab
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h
@@ -0,0 +1,1384 @@
+/* Generated by wayland-scanner 1.24.0 */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_CLIENT_PROTOCOL_H
+#define KDE_OUTPUT_DEVICE_V2_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <wayland-client.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_kde_output_device_v2 The kde_output_device_v2 protocol
+ * @section page_ifaces_kde_output_device_v2 Interfaces
+ * - @subpage page_iface_kde_output_device_registry_v2 - output devices
+ * - @subpage page_iface_kde_output_device_v2 - output configuration representation
+ * - @subpage page_iface_kde_output_device_mode_v2 - output mode
+ * @section page_copyright_kde_output_device_v2 Copyright
+ * <pre>
+ *
+ * SPDX-FileCopyrightText: 2008-2011 Kristian Høgsberg
+ * SPDX-FileCopyrightText: 2010-2011 Intel Corporation
+ * SPDX-FileCopyrightText: 2012-2013 Collabora, Ltd.
+ * SPDX-FileCopyrightText: 2015 Sebastian Kügler <sebas@kde.org>
+ * SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
+ *
+ * SPDX-License-Identifier: MIT-CMU
+ * </pre>
+ */
+struct kde_output_device_mode_v2;
+struct kde_output_device_registry_v2;
+struct kde_output_device_v2;
+
+#ifndef KDE_OUTPUT_DEVICE_REGISTRY_V2_INTERFACE
+ #define KDE_OUTPUT_DEVICE_REGISTRY_V2_INTERFACE
+/**
+ * @page page_iface_kde_output_device_registry_v2 kde_output_device_registry_v2
+ * @section page_iface_kde_output_device_registry_v2_desc Description
+ *
+ * This interface can be used to list output devices.
+ *
+ * If this global is bound with a version less than 21, the unsupported_version
+ * protocol error will be posted.
+ * @section page_iface_kde_output_device_registry_v2_api API
+ * See @ref iface_kde_output_device_registry_v2.
+ */
+/**
+ * @defgroup iface_kde_output_device_registry_v2 The kde_output_device_registry_v2 interface
+ *
+ * This interface can be used to list output devices.
+ *
+ * If this global is bound with a version less than 21, the unsupported_version
+ * protocol error will be posted.
+ */
+extern const struct wl_interface kde_output_device_registry_v2_interface;
+#endif
+#ifndef KDE_OUTPUT_DEVICE_V2_INTERFACE
+ #define KDE_OUTPUT_DEVICE_V2_INTERFACE
+/**
+ * @page page_iface_kde_output_device_v2 kde_output_device_v2
+ * @section page_iface_kde_output_device_v2_desc Description
+ *
+ * An output device describes a display device available to the compositor.
+ * output_device is similar to wl_output, but focuses on output
+ * configuration management.
+ *
+ * A client can query all global output_device objects to enlist all
+ * available display devices, even those that may currently not be
+ * represented by the compositor as a wl_output.
+ *
+ * The client sends configuration changes to the server through the
+ * outputconfiguration interface, and the server applies the configuration
+ * changes to the hardware and signals changes to the output devices
+ * accordingly.
+ *
+ * This object is published as global during start up for every available
+ * display devices, or when one later becomes available, for example by
+ * being hotplugged via a physical connector.
+ *
+ * Warning! The protocol described in this file is a desktop environment
+ * implementation detail. Regular clients must not use this protocol.
+ * Backward incompatible changes may be added without bumping the major
+ * version of the extension.
+ * @section page_iface_kde_output_device_v2_api API
+ * See @ref iface_kde_output_device_v2.
+ */
+/**
+ * @defgroup iface_kde_output_device_v2 The kde_output_device_v2 interface
+ *
+ * An output device describes a display device available to the compositor.
+ * output_device is similar to wl_output, but focuses on output
+ * configuration management.
+ *
+ * A client can query all global output_device objects to enlist all
+ * available display devices, even those that may currently not be
+ * represented by the compositor as a wl_output.
+ *
+ * The client sends configuration changes to the server through the
+ * outputconfiguration interface, and the server applies the configuration
+ * changes to the hardware and signals changes to the output devices
+ * accordingly.
+ *
+ * This object is published as global during start up for every available
+ * display devices, or when one later becomes available, for example by
+ * being hotplugged via a physical connector.
+ *
+ * Warning! The protocol described in this file is a desktop environment
+ * implementation detail. Regular clients must not use this protocol.
+ * Backward incompatible changes may be added without bumping the major
+ * version of the extension.
+ */
+extern const struct wl_interface kde_output_device_v2_interface;
+#endif
+#ifndef KDE_OUTPUT_DEVICE_MODE_V2_INTERFACE
+ #define KDE_OUTPUT_DEVICE_MODE_V2_INTERFACE
+/**
+ * @page page_iface_kde_output_device_mode_v2 kde_output_device_mode_v2
+ * @section page_iface_kde_output_device_mode_v2_desc Description
+ *
+ * This object describes an output mode.
+ *
+ * Some heads don't support output modes, in which case modes won't be
+ * advertised.
+ *
+ * Properties sent via this interface are applied atomically via the
+ * kde_output_device.done event. No guarantees are made regarding the order
+ * in which properties are sent.
+ * @section page_iface_kde_output_device_mode_v2_api API
+ * See @ref iface_kde_output_device_mode_v2.
+ */
+/**
+ * @defgroup iface_kde_output_device_mode_v2 The kde_output_device_mode_v2 interface
+ *
+ * This object describes an output mode.
+ *
+ * Some heads don't support output modes, in which case modes won't be
+ * advertised.
+ *
+ * Properties sent via this interface are applied atomically via the
+ * kde_output_device.done event. No guarantees are made regarding the order
+ * in which properties are sent.
+ */
+extern const struct wl_interface kde_output_device_mode_v2_interface;
+#endif
+
+#ifndef KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM
+ #define KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM
+/**
+ * @ingroup iface_kde_output_device_registry_v2
+ * kde_output_device_registry_v2 error values
+ *
+ * These errors can be emitted in response to some requests.
+ */
+enum kde_output_device_registry_v2_error {
+ /**
+ * the registry was bound with an unsupported version
+ */
+ KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_UNSUPPORTED_VERSION = 0,
+};
+#endif /* KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM */
+
+/**
+ * @ingroup iface_kde_output_device_registry_v2
+ * @struct kde_output_device_registry_v2_listener
+ */
+struct kde_output_device_registry_v2_listener {
+ /**
+ * no new output announcements
+ *
+ * This event is sent in response to the stop request. The
+ * compositor will immediately destroy the object after sending
+ * this event.
+ * @since 21
+ */
+ void (*finished)(void* data,
+ struct kde_output_device_registry_v2* kde_output_device_registry_v2);
+ /**
+ * new available output
+ *
+ * This event is sent when a new output is connected or after
+ * binding this global to list all available outputs.
+ * @since 21
+ */
+ void (*output)(void* data,
+ struct kde_output_device_registry_v2* kde_output_device_registry_v2,
+ struct kde_output_device_v2* output);
+};
+
+/**
+ * @ingroup iface_kde_output_device_registry_v2
+ */
+static inline int
+kde_output_device_registry_v2_add_listener(struct kde_output_device_registry_v2* kde_output_device_registry_v2,
+ const struct kde_output_device_registry_v2_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) kde_output_device_registry_v2,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP 0
+
+/**
+ * @ingroup iface_kde_output_device_registry_v2
+ */
+#define KDE_OUTPUT_DEVICE_REGISTRY_V2_FINISHED_SINCE_VERSION 21
+/**
+ * @ingroup iface_kde_output_device_registry_v2
+ */
+#define KDE_OUTPUT_DEVICE_REGISTRY_V2_OUTPUT_SINCE_VERSION 21
+
+/**
+ * @ingroup iface_kde_output_device_registry_v2
+ */
+#define KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP_SINCE_VERSION 21
+
+/** @ingroup iface_kde_output_device_registry_v2 */
+static inline void
+kde_output_device_registry_v2_set_user_data(struct kde_output_device_registry_v2* kde_output_device_registry_v2, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) kde_output_device_registry_v2, user_data);
+}
+
+/** @ingroup iface_kde_output_device_registry_v2 */
+static inline void*
+kde_output_device_registry_v2_get_user_data(struct kde_output_device_registry_v2* kde_output_device_registry_v2) {
+ return wl_proxy_get_user_data((struct wl_proxy*) kde_output_device_registry_v2);
+}
+
+static inline uint32_t
+kde_output_device_registry_v2_get_version(struct kde_output_device_registry_v2* kde_output_device_registry_v2) {
+ return wl_proxy_get_version((struct wl_proxy*) kde_output_device_registry_v2);
+}
+
+/** @ingroup iface_kde_output_device_registry_v2 */
+static inline void
+kde_output_device_registry_v2_destroy(struct kde_output_device_registry_v2* kde_output_device_registry_v2) {
+ wl_proxy_destroy((struct wl_proxy*) kde_output_device_registry_v2);
+}
+
+/**
+ * @ingroup iface_kde_output_device_registry_v2
+ *
+ * This request indicates that the client no longer wants to receive new
+ * output announcements. The compositor will send the
+ * kde_output_device_registry_v2.finished event in response to this request.
+ * The compositor may still send new output announcements after calling this
+ * request until the kde_output_device_registry_v2.finished event is sent.
+ */
+// static inline void
+// kde_output_device_registry_v2_stop(struct kde_output_device_registry_v2* kde_output_device_registry_v2) {
+// wl_proxy_marshal_flags((struct wl_proxy*) kde_output_device_registry_v2,
+// KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP,
+// NULL,
+// wl_proxy_get_version((struct wl_proxy*) kde_output_device_registry_v2),
+// 0);
+// }
+
+#ifndef KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * subpixel geometry information
+ *
+ * This enumeration describes how the physical pixels on an output are
+ * laid out.
+ */
+enum kde_output_device_v2_subpixel {
+ KDE_OUTPUT_DEVICE_V2_SUBPIXEL_UNKNOWN = 0,
+ KDE_OUTPUT_DEVICE_V2_SUBPIXEL_NONE = 1,
+ KDE_OUTPUT_DEVICE_V2_SUBPIXEL_HORIZONTAL_RGB = 2,
+ KDE_OUTPUT_DEVICE_V2_SUBPIXEL_HORIZONTAL_BGR = 3,
+ KDE_OUTPUT_DEVICE_V2_SUBPIXEL_VERTICAL_RGB = 4,
+ KDE_OUTPUT_DEVICE_V2_SUBPIXEL_VERTICAL_BGR = 5,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * transform from framebuffer to output
+ *
+ * This describes the transform, that a compositor will apply to a
+ * surface to compensate for the rotation or mirroring of an
+ * output device.
+ *
+ * The flipped values correspond to an initial flip around a
+ * vertical axis followed by rotation.
+ *
+ * The purpose is mainly to allow clients to render accordingly and
+ * tell the compositor, so that for fullscreen surfaces, the
+ * compositor is still able to scan out directly client surfaces.
+ */
+enum kde_output_device_v2_transform {
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_NORMAL = 0,
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_90 = 1,
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_180 = 2,
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_270 = 3,
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED = 4,
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_90 = 5,
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_180 = 6,
+ KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_270 = 7,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * describes capabilities of the outputdevice
+ *
+ * Describes what capabilities this device has.
+ */
+enum kde_output_device_v2_capability {
+ /**
+ * if this output_device can use overscan
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_OVERSCAN = 0x1,
+ /**
+ * if this outputdevice supports variable refresh rate
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_VRR = 0x2,
+ /**
+ * if setting the rgb range is possible
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_RGB_RANGE = 0x4,
+ /**
+ * if this outputdevice supports high dynamic range
+ * @since 3
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_HIGH_DYNAMIC_RANGE = 0x8,
+ /**
+ * if this outputdevice supports a wide color gamut
+ * @since 3
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_WIDE_COLOR_GAMUT = 0x10,
+ /**
+ * if this outputdevice supports autorotation
+ * @since 4
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_ROTATE = 0x20,
+ /**
+ * if this outputdevice supports icc profiles
+ * @since 5
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_ICC_PROFILE = 0x40,
+ /**
+ * if this outputdevice supports the brightness setting
+ * @since 9
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_BRIGHTNESS = 0x80,
+ /**
+ * if this outputdevice supports the built-in color profile
+ * @since 12
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_BUILT_IN_COLOR = 0x100,
+ /**
+ * if this outputdevice supports DDC/CI
+ * @since 14
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_DDC_CI = 0x200,
+ /**
+ * if this outputdevice supports setting max bpc
+ * @since 15
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_MAX_BITS_PER_COLOR = 0x400,
+ /**
+ * if this outputdevice supports EDR
+ * @since 16
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_EDR = 0x800,
+ /**
+ * if this outputdevice supports the sharpness setting
+ * @since 17
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_SHARPNESS = 0x1000,
+ /**
+ * if this outputdevice supports custom modes
+ * @since 18
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_CUSTOM_MODES = 0x2000,
+ /**
+ * @since 19
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS = 0x4000,
+ /**
+ * if this outputdevice supports HDR ICC profiles
+ * @since 22
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_HDR_ICC_PROFILE = 0x8000,
+ /**
+ * if this outputdevice supports the abm level setting
+ * @since 23
+ */
+ KDE_OUTPUT_DEVICE_V2_CAPABILITY_ABM_LEVEL = 0x10000,
+};
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_HIGH_DYNAMIC_RANGE_SINCE_VERSION 3
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_WIDE_COLOR_GAMUT_SINCE_VERSION 3
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_ROTATE_SINCE_VERSION 4
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ICC_PROFILE_SINCE_VERSION 5
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_BRIGHTNESS_SINCE_VERSION 9
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_BUILT_IN_COLOR_SINCE_VERSION 12
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_DDC_CI_SINCE_VERSION 14
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_MAX_BITS_PER_COLOR_SINCE_VERSION 15
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_EDR_SINCE_VERSION 16
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_SHARPNESS_SINCE_VERSION 17
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_CUSTOM_MODES_SINCE_VERSION 18
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS_SINCE_VERSION 19
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_HDR_ICC_PROFILE_SINCE_VERSION 22
+ /**
+ * @ingroup iface_kde_output_device_v2
+ */
+ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ABM_LEVEL_SINCE_VERSION 23
+#endif /* KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * describes vrr policy
+ *
+ * Describes when the compositor may employ variable refresh rate
+ */
+enum kde_output_device_v2_vrr_policy {
+ KDE_OUTPUT_DEVICE_V2_VRR_POLICY_NEVER = 0,
+ KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ALWAYS = 1,
+ KDE_OUTPUT_DEVICE_V2_VRR_POLICY_AUTOMATIC = 2,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * describes RGB range policy
+ *
+ * Whether full or limited color range should be used
+ */
+enum kde_output_device_v2_rgb_range {
+ KDE_OUTPUT_DEVICE_V2_RGB_RANGE_AUTOMATIC = 0,
+ KDE_OUTPUT_DEVICE_V2_RGB_RANGE_FULL = 1,
+ KDE_OUTPUT_DEVICE_V2_RGB_RANGE_LIMITED = 2,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * describes when auto rotate should be used
+ */
+enum kde_output_device_v2_auto_rotate_policy {
+ KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_NEVER = 0,
+ KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_IN_TABLET_MODE = 1,
+ KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ALWAYS = 2,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * which source the compositor should use for the color profile on an output
+ */
+enum kde_output_device_v2_color_profile_source {
+ KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_SRGB = 0,
+ KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ICC = 1,
+ KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_EDID = 2,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * tradeoff between power and accuracy
+ *
+ * The compositor can do a lot of things that trade between
+ * performance, power and color accuracy. This setting describes
+ * a high level preference from the user about in which direction
+ * that tradeoff should be made.
+ */
+enum kde_output_device_v2_color_power_tradeoff {
+ /**
+ * prefer efficiency and performance
+ */
+ KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_EFFICIENCY = 0,
+ /**
+ * prefer accuracy
+ */
+ KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ACCURACY = 1,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM */
+
+#ifndef KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM
+ #define KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM
+/**
+ * @ingroup iface_kde_output_device_v2
+ * when the compositor may make use of EDR
+ */
+enum kde_output_device_v2_edr_policy {
+ KDE_OUTPUT_DEVICE_V2_EDR_POLICY_NEVER = 0,
+ KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ALWAYS = 1,
+};
+#endif /* KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM */
+
+/**
+ * @ingroup iface_kde_output_device_v2
+ * @struct kde_output_device_v2_listener
+ */
+struct kde_output_device_v2_listener {
+ /**
+ * geometric properties of the output
+ *
+ * The geometry event describes geometric properties of the
+ * output. The event is sent when binding to the output object and
+ * whenever any of the properties change.
+ * @param x x position within the global compositor space
+ * @param y y position within the global compositor space
+ * @param physical_width width in millimeters of the output
+ * @param physical_height height in millimeters of the output
+ * @param subpixel subpixel orientation of the output
+ * @param make textual description of the manufacturer
+ * @param model textual description of the model
+ * @param transform transform that maps framebuffer to output
+ */
+ void (*geometry)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ int32_t x,
+ int32_t y,
+ int32_t physical_width,
+ int32_t physical_height,
+ int32_t subpixel,
+ const char* make,
+ const char* model,
+ int32_t transform);
+ /**
+ * current mode
+ *
+ * This event describes the mode currently in use for this head.
+ * It is only sent if the output is enabled.
+ */
+ void (*current_mode)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ struct kde_output_device_mode_v2* mode);
+ /**
+ * advertise available output modes and current one
+ *
+ * The mode event describes an available mode for the output.
+ *
+ * When the client binds to the output_device object, the server
+ * sends this event once for every available mode the output_device
+ * can be operated by.
+ *
+ * There will always be at least one event sent out on initial
+ * binding, which represents the current mode.
+ *
+ * Later if an output changes, its mode event is sent again for the
+ * eventual added modes and lastly the current mode. In other
+ * words, the current mode is always represented by the latest
+ * event sent with the current flag set.
+ *
+ * The size of a mode is given in physical hardware units of the
+ * output device. This is not necessarily the same as the output
+ * size in the global compositor space. For instance, the output
+ * may be scaled, as described in kde_output_device_v2.scale, or
+ * transformed, as described in kde_output_device_v2.transform.
+ */
+ void (*mode)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ struct kde_output_device_mode_v2* mode);
+ /**
+ * sent all information about output
+ *
+ * This event is sent after all other properties have been sent
+ * on binding to the output object as well as after any other
+ * output property change have been applied later on. This allows
+ * to see changes to the output properties as atomic, even if
+ * multiple events successively announce them.
+ */
+ void (*done)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2);
+ /**
+ * output scaling properties
+ *
+ * This event contains scaling geometry information that is not
+ * in the geometry event. It may be sent after binding the output
+ * object or if the output scale changes later. If it is not sent,
+ * the client should assume a scale of 1.
+ *
+ * A scale larger than 1 means that the compositor will
+ * automatically scale surface buffers by this amount when
+ * rendering. This is used for high resolution displays where
+ * applications rendering at the native resolution would be too
+ * small to be legible.
+ *
+ * It is intended that scaling aware clients track the current
+ * output of a surface, and if it is on a scaled output it should
+ * use wl_surface.set_buffer_scale with the scale of the output.
+ * That way the compositor can avoid scaling the surface, and the
+ * client can supply a higher detail image.
+ * @param factor scaling factor of output
+ */
+ void (*scale)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ wl_fixed_t factor);
+ /**
+ * advertise EDID data for the output
+ *
+ * The edid event encapsulates the EDID data for the
+ * outputdevice.
+ *
+ * The event is sent when binding to the output object. The EDID
+ * data may be empty, in which case this event is sent anyway. If
+ * the EDID information is empty, you can fall back to the name et
+ * al. properties of the outputdevice.
+ * @param raw base64-encoded EDID string
+ */
+ void (*edid)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* raw);
+ /**
+ * output is enabled or disabled
+ *
+ * The enabled event notifies whether this output is currently
+ * enabled and used for displaying content by the server. The event
+ * is sent when binding to the output object and whenever later on
+ * an output changes its state by becoming enabled or disabled.
+ * @param enabled output enabled state
+ */
+ void (*enabled)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ int32_t enabled);
+ /**
+ * A unique id for this outputdevice
+ *
+ * The uuid can be used to identify the output. It's controlled
+ * by the server entirely. The server should make sure the uuid is
+ * persistent across restarts. An empty uuid is considered invalid.
+ * @param uuid output devices ID
+ */
+ void (*uuid)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* uuid);
+ /**
+ * Serial Number
+ *
+ * Serial ID of the monitor, sent on startup before the first
+ * done event.
+ * @param serialNumber textual representation of serial number
+ */
+ void (*serial_number)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* serialNumber);
+ /**
+ * EISA ID
+ *
+ * EISA ID of the monitor, sent on startup before the first done
+ * event.
+ * @param eisaId textual representation of EISA identifier
+ */
+ void (*eisa_id)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* eisaId);
+ /**
+ * capability flags
+ *
+ * What capabilities this device has, sent on startup before the
+ * first done event.
+ */
+ void (*capabilities)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t flags);
+ /**
+ * overscan
+ *
+ * Overscan value of the monitor in percent, sent on startup
+ * before the first done event.
+ * @param overscan amount of overscan of the monitor
+ */
+ void (*overscan)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t overscan);
+ /**
+ * Variable Refresh Rate Policy
+ *
+ * What policy the compositor will employ regarding its use of
+ * variable refresh rate.
+ */
+ void (*vrr_policy)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t vrr_policy);
+ /**
+ * RGB range
+ *
+ * What rgb range the compositor is using for this output
+ */
+ void (*rgb_range)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t rgb_range);
+ /**
+ * Output's name
+ *
+ * Name of the output, it's useful to cross-reference to an
+ * zxdg_output_v1 and ultimately QScreen
+ * @since 2
+ */
+ void (*name)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* name);
+ /**
+ * if HDR is enabled
+ *
+ * Whether or not high dynamic range is enabled for this output
+ * @param hdr_enabled 1 if enabled, 0 if disabled
+ * @since 3
+ */
+ void (*high_dynamic_range)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t hdr_enabled);
+ /**
+ * the brightness of sdr if hdr is enabled
+ *
+ * If high dynamic range is used, this value defines the
+ * brightness in nits for content that's in standard dynamic range
+ * format. Note that while the value is in nits, that doesn't
+ * necessarily translate to the same brightness on the screen.
+ * @since 3
+ */
+ void (*sdr_brightness)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t sdr_brightness);
+ /**
+ * if WCG is enabled
+ *
+ * Whether or not the use of a wide color gamut is enabled for
+ * this output
+ * @param wcg_enabled 1 if enabled, 0 if disabled
+ * @since 3
+ */
+ void (*wide_color_gamut)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t wcg_enabled);
+ /**
+ * describes when auto rotate is used
+ *
+ *
+ * @since 4
+ */
+ void (*auto_rotate_policy)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t policy);
+ /**
+ * describes the path to the ICC profile used in SDR mode
+ *
+ *
+ * @since 5
+ */
+ void (*icc_profile_path)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* profile_path);
+ /**
+ * metadata about the screen's brightness limits
+ *
+ *
+ * @param max_peak_brightness in nits
+ * @param max_frame_average_brightness in nits
+ * @param min_brightness in 0.0001 nits
+ * @since 6
+ */
+ void (*brightness_metadata)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t max_peak_brightness,
+ uint32_t max_frame_average_brightness,
+ uint32_t min_brightness);
+ /**
+ * overrides for the screen's brightness limits
+ *
+ *
+ * @param max_peak_brightness -1 for no override, positive values are the brightness in nits
+ * @param max_average_brightness -1 for no override, positive values are the brightness in nits
+ * @param min_brightness -1 for no override, positive values are the brightness in 0.0001 nits
+ * @since 6
+ */
+ void (*brightness_overrides)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ int32_t max_peak_brightness,
+ int32_t max_average_brightness,
+ int32_t min_brightness);
+ /**
+ * describes which gamut is assumed for sRGB applications
+ *
+ * This can be used to provide the colors users assume sRGB
+ * applications should have based on the default experience on many
+ * modern sRGB screens.
+ * @param gamut_wideness 0 means rec.709 primaries, 10000 means native primaries
+ * @since 6
+ */
+ void (*sdr_gamut_wideness)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t gamut_wideness);
+ /**
+ * describes which source the compositor uses for the color profile on an output in SDR mode
+ *
+ *
+ * @since 7
+ */
+ void (*color_profile_source)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t source);
+ /**
+ * brightness multiplier
+ *
+ * This is the brightness modifier of the output. It doesn't
+ * specify any absolute values, but is merely a multiplier on top
+ * of other brightness values, like sdr_brightness and
+ * brightness_metadata. 0 is the minimum brightness (not completely
+ * dark) and 10000 is the maximum brightness. This is currently
+ * only supported / meaningful while HDR is active.
+ * @param brightness brightness in 0-10000
+ * @since 8
+ */
+ void (*brightness)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t brightness);
+ /**
+ * the preferred color/power tradeoff
+ *
+ *
+ * @since 10
+ */
+ void (*color_power_tradeoff)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t preference);
+ /**
+ * dimming multiplier
+ *
+ * This is the dimming multiplier of the output. This is similar
+ * to the brightness setting, except it's meant to be a temporary
+ * setting only, not persistent and may be implemented differently
+ * depending on the display. 0 is the minimum dimming factor (not
+ * completely dark) and 10000 means the output is not dimmed.
+ * @param multiplier multiplier in 0-10000
+ * @since 11
+ */
+ void (*dimming)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t multiplier);
+ /**
+ * source output for mirroring
+ *
+ *
+ * @param source uuid of the source output
+ * @since 13
+ */
+ void (*replication_source)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* source);
+ /**
+ * if DDC/CI should be used to control brightness etc.
+ *
+ * If the ddc_ci capability is present, this determines if
+ * settings such as brightness, contrast or others should be set
+ * using DDC/CI.
+ * @param allowed 1 if allowed, 0 if disabled
+ * @since 14
+ */
+ void (*ddc_ci_allowed)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t allowed);
+ /**
+ * override max bpc
+ *
+ * This limits the amount of bits per color that are sent to the
+ * display.
+ * @param max_bpc 0 for the default / automatic
+ * @since 15
+ */
+ void (*max_bits_per_color)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t max_bpc);
+ /**
+ * range of max bits per color value
+ *
+ *
+ * @param min_value the minimum supported by the driver
+ * @param max_value the maximum supported by the driver
+ * @since 15
+ */
+ void (*max_bits_per_color_range)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t min_value,
+ uint32_t max_value);
+ /**
+ * if and to what value automatic max bpc is limited
+ *
+ *
+ * @param max_bpc_limit which value automatic bpc gets limited to. 0 if not limited
+ * @since 15
+ */
+ void (*automatic_max_bits_per_color_limit)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t max_bpc_limit);
+ /**
+ * when the compositor may apply EDR
+ *
+ * When EDR is enabled, the compositor may increase the backlight
+ * beyond the user-specified setting, in order to present HDR
+ * content on displays without native HDR support. This will
+ * usually result in better visuals, but also increases battery
+ * usage.
+ * @since 16
+ */
+ void (*edr_policy)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t policy);
+ /**
+ * sharpness strength
+ *
+ * This is the sharpness modifier of the output. 0 is sharpness
+ * disabled and 10000 is the maximum sharpness
+ * @param sharpness sharpness in 0-10000
+ * @since 17
+ */
+ void (*sharpness)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t sharpness);
+ /**
+ * output priority
+ *
+ * Describes the position of the output in the output order list,
+ * with lower values being earlier in the list. There's no specific
+ * value the list has to start at, this value is only used in
+ * sorting outputs.
+ *
+ * Note that the output order protocol is not sufficient for this,
+ * as an output may not be in the output order if it's disabled or
+ * mirroring another screen.
+ * @param priority priority
+ * @since 18
+ */
+ void (*priority)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t priority);
+ /**
+ * whether or not automatic brightness is enabled
+ *
+ *
+ * @param enabled 1 for enabled, 0 for disabled
+ * @since 20
+ */
+ void (*auto_brightness)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t enabled);
+ /**
+ * the output has been removed
+ *
+ * This event is sent when the output device is disconnected and
+ * no new updates will be sent. The client should call the
+ * kde_output_device_v2.release request after receiving this event.
+ * @since 21
+ */
+ void (*removed)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2);
+ /**
+ * describes the path to the ICC profile used in HDR mode
+ *
+ *
+ * @since 22
+ */
+ void (*hdr_icc_profile_path)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ const char* profile_path);
+ /**
+ * describes which source the compositor uses for the color profile on an output in HDR mode
+ *
+ *
+ * @since 22
+ */
+ void (*hdr_color_profile_source)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t source);
+ /**
+ * allowed level of adaptive backlight modulation
+ *
+ * Adaptive backlight modulation is a feature that reduces the
+ * backlight and increases contrast of colors on the screen to
+ * improve power usage.
+ * @param level 0 is off, 4 is the maximum level
+ * @since 23
+ */
+ void (*abm_level)(void* data,
+ struct kde_output_device_v2* kde_output_device_v2,
+ uint32_t level);
+};
+
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+static inline int
+kde_output_device_v2_add_listener(struct kde_output_device_v2* kde_output_device_v2,
+ const struct kde_output_device_v2_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) kde_output_device_v2,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define KDE_OUTPUT_DEVICE_V2_RELEASE 0
+
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_GEOMETRY_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_CURRENT_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_SCALE_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_EDID_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_ENABLED_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_UUID_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_SERIAL_NUMBER_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_EISA_ID_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_CAPABILITIES_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_OVERSCAN_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_VRR_POLICY_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_RGB_RANGE_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_NAME_SINCE_VERSION 2
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_HIGH_DYNAMIC_RANGE_SINCE_VERSION 3
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_SDR_BRIGHTNESS_SINCE_VERSION 3
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_WIDE_COLOR_GAMUT_SINCE_VERSION 3
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_SINCE_VERSION 4
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_ICC_PROFILE_PATH_SINCE_VERSION 5
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_METADATA_SINCE_VERSION 6
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_OVERRIDES_SINCE_VERSION 6
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_SDR_GAMUT_WIDENESS_SINCE_VERSION 6
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_SINCE_VERSION 7
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_SINCE_VERSION 8
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_SINCE_VERSION 10
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_DIMMING_SINCE_VERSION 11
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_REPLICATION_SOURCE_SINCE_VERSION 13
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_DDC_CI_ALLOWED_SINCE_VERSION 14
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_SINCE_VERSION 15
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_RANGE_SINCE_VERSION 15
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_AUTOMATIC_MAX_BITS_PER_COLOR_LIMIT_SINCE_VERSION 15
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_EDR_POLICY_SINCE_VERSION 16
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_SHARPNESS_SINCE_VERSION 17
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_PRIORITY_SINCE_VERSION 18
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_AUTO_BRIGHTNESS_SINCE_VERSION 20
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_REMOVED_SINCE_VERSION 21
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_HDR_ICC_PROFILE_PATH_SINCE_VERSION 22
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_HDR_COLOR_PROFILE_SOURCE_SINCE_VERSION 22
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_ABM_LEVEL_SINCE_VERSION 23
+
+/**
+ * @ingroup iface_kde_output_device_v2
+ */
+#define KDE_OUTPUT_DEVICE_V2_RELEASE_SINCE_VERSION 21
+
+/** @ingroup iface_kde_output_device_v2 */
+static inline void
+kde_output_device_v2_set_user_data(struct kde_output_device_v2* kde_output_device_v2, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) kde_output_device_v2, user_data);
+}
+
+/** @ingroup iface_kde_output_device_v2 */
+static inline void*
+kde_output_device_v2_get_user_data(struct kde_output_device_v2* kde_output_device_v2) {
+ return wl_proxy_get_user_data((struct wl_proxy*) kde_output_device_v2);
+}
+
+static inline uint32_t
+kde_output_device_v2_get_version(struct kde_output_device_v2* kde_output_device_v2) {
+ return wl_proxy_get_version((struct wl_proxy*) kde_output_device_v2);
+}
+
+/** @ingroup iface_kde_output_device_v2 */
+static inline void
+kde_output_device_v2_destroy(struct kde_output_device_v2* kde_output_device_v2) {
+ wl_proxy_destroy((struct wl_proxy*) kde_output_device_v2);
+}
+
+/**
+ * @ingroup iface_kde_output_device_v2
+ *
+ * This notifies the compositor that the client no longer wishes to use
+ * the kde_output_device_v2 object.
+ */
+// static inline void
+// kde_output_device_v2_release(struct kde_output_device_v2* kde_output_device_v2) {
+// wl_proxy_marshal_flags((struct wl_proxy*) kde_output_device_v2,
+// KDE_OUTPUT_DEVICE_V2_RELEASE,
+// NULL,
+// wl_proxy_get_version((struct wl_proxy*) kde_output_device_v2),
+// WL_MARSHAL_FLAG_DESTROY);
+// }
+
+#ifndef KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM
+ #define KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ * mode flags
+ */
+enum kde_output_device_mode_v2_flags {
+ KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_CUSTOM = 0x1,
+ KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_REDUCED_BLANKING = 0x2,
+};
+#endif /* KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM */
+
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ * @struct kde_output_device_mode_v2_listener
+ */
+struct kde_output_device_mode_v2_listener {
+ /**
+ * mode size
+ *
+ * This event describes the mode size. The size is given in
+ * physical hardware units of the output device. This is not
+ * necessarily the same as the output size in the global compositor
+ * space. For instance, the output may be scaled or transformed.
+ * @param width width of the mode in hardware units
+ * @param height height of the mode in hardware units
+ */
+ void (*size)(void* data,
+ struct kde_output_device_mode_v2* kde_output_device_mode_v2,
+ int32_t width,
+ int32_t height);
+ /**
+ * mode refresh rate
+ *
+ * This event describes the mode's fixed vertical refresh rate.
+ * It is only sent if the mode has a fixed refresh rate.
+ * @param refresh vertical refresh rate in mHz
+ */
+ void (*refresh)(void* data,
+ struct kde_output_device_mode_v2* kde_output_device_mode_v2,
+ int32_t refresh);
+ /**
+ * mode is preferred
+ *
+ * This event advertises this mode as preferred.
+ */
+ void (*preferred)(void* data,
+ struct kde_output_device_mode_v2* kde_output_device_mode_v2);
+ /**
+ * the mode has been destroyed
+ *
+ * The compositor will destroy the object immediately after
+ * sending this event, so it will become invalid and the client
+ * should release any resources associated with it.
+ */
+ void (*removed)(void* data,
+ struct kde_output_device_mode_v2* kde_output_device_mode_v2);
+ /**
+ * mode flags
+ *
+ * This event describes the mode's flags.
+ * @since 19
+ */
+ void (*flags)(void* data,
+ struct kde_output_device_mode_v2* kde_output_device_mode_v2,
+ uint32_t flags);
+};
+
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ */
+static inline int
+kde_output_device_mode_v2_add_listener(struct kde_output_device_mode_v2* kde_output_device_mode_v2,
+ const struct kde_output_device_mode_v2_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) kde_output_device_mode_v2,
+ (void (**)(void)) listener,
+ data);
+}
+
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ */
+#define KDE_OUTPUT_DEVICE_MODE_V2_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ */
+#define KDE_OUTPUT_DEVICE_MODE_V2_REFRESH_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ */
+#define KDE_OUTPUT_DEVICE_MODE_V2_PREFERRED_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ */
+#define KDE_OUTPUT_DEVICE_MODE_V2_REMOVED_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_device_mode_v2
+ */
+#define KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_SINCE_VERSION 19
+
+/** @ingroup iface_kde_output_device_mode_v2 */
+static inline void
+kde_output_device_mode_v2_set_user_data(struct kde_output_device_mode_v2* kde_output_device_mode_v2, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) kde_output_device_mode_v2, user_data);
+}
+
+/** @ingroup iface_kde_output_device_mode_v2 */
+static inline void*
+kde_output_device_mode_v2_get_user_data(struct kde_output_device_mode_v2* kde_output_device_mode_v2) {
+ return wl_proxy_get_user_data((struct wl_proxy*) kde_output_device_mode_v2);
+}
+
+static inline uint32_t
+kde_output_device_mode_v2_get_version(struct kde_output_device_mode_v2* kde_output_device_mode_v2) {
+ return wl_proxy_get_version((struct wl_proxy*) kde_output_device_mode_v2);
+}
+
+/** @ingroup iface_kde_output_device_mode_v2 */
+static inline void
+kde_output_device_mode_v2_destroy(struct kde_output_device_mode_v2* kde_output_device_mode_v2) {
+ wl_proxy_destroy((struct wl_proxy*) kde_output_device_mode_v2);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c b/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c
new file mode 100644
index 0000000..9dd7556
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c
@@ -0,0 +1,128 @@
+#ifdef FF_HAVE_WAYLAND
+
+/* Generated by wayland-scanner 1.24.0 */
+
+/*
+ * SPDX-FileCopyrightText: 2008-2011 Kristian Høgsberg
+ * SPDX-FileCopyrightText: 2010-2011 Intel Corporation
+ * SPDX-FileCopyrightText: 2012-2013 Collabora, Ltd.
+ * SPDX-FileCopyrightText: 2015 Sebastian Kügler <sebas@kde.org>
+ * SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
+ *
+ * SPDX-License-Identifier: MIT-CMU
+ */
+
+ #include <stdbool.h>
+ #include <stdlib.h>
+ #include <stdint.h>
+ #include <wayland-util.h>
+
+extern const struct wl_interface kde_output_device_mode_v2_interface;
+extern const struct wl_interface kde_output_device_v2_interface;
+
+static const struct wl_interface* kde_output_device_v2_types[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &kde_output_device_v2_interface,
+ &kde_output_device_mode_v2_interface,
+ &kde_output_device_mode_v2_interface,
+};
+
+static const struct wl_message kde_output_device_registry_v2_requests[] = {
+ { "stop", "21", kde_output_device_v2_types + 0 },
+};
+
+static const struct wl_message kde_output_device_registry_v2_events[] = {
+ { "finished", "21", kde_output_device_v2_types + 0 },
+ { "output", "21n", kde_output_device_v2_types + 8 },
+};
+
+WL_EXPORT const struct wl_interface kde_output_device_registry_v2_interface = {
+ "kde_output_device_registry_v2",
+ 23,
+ 1,
+ kde_output_device_registry_v2_requests,
+ 2,
+ kde_output_device_registry_v2_events,
+};
+
+static const struct wl_message kde_output_device_v2_requests[] = {
+ { "release", "21", kde_output_device_v2_types + 0 },
+};
+
+static const struct wl_message kde_output_device_v2_events[] = {
+ { "geometry", "iiiiissi", kde_output_device_v2_types + 0 },
+ { "current_mode", "o", kde_output_device_v2_types + 9 },
+ { "mode", "n", kde_output_device_v2_types + 10 },
+ { "done", "", kde_output_device_v2_types + 0 },
+ { "scale", "f", kde_output_device_v2_types + 0 },
+ { "edid", "s", kde_output_device_v2_types + 0 },
+ { "enabled", "i", kde_output_device_v2_types + 0 },
+ { "uuid", "s", kde_output_device_v2_types + 0 },
+ { "serial_number", "s", kde_output_device_v2_types + 0 },
+ { "eisa_id", "s", kde_output_device_v2_types + 0 },
+ { "capabilities", "u", kde_output_device_v2_types + 0 },
+ { "overscan", "u", kde_output_device_v2_types + 0 },
+ { "vrr_policy", "u", kde_output_device_v2_types + 0 },
+ { "rgb_range", "u", kde_output_device_v2_types + 0 },
+ { "name", "2s", kde_output_device_v2_types + 0 },
+ { "high_dynamic_range", "3u", kde_output_device_v2_types + 0 },
+ { "sdr_brightness", "3u", kde_output_device_v2_types + 0 },
+ { "wide_color_gamut", "3u", kde_output_device_v2_types + 0 },
+ { "auto_rotate_policy", "4u", kde_output_device_v2_types + 0 },
+ { "icc_profile_path", "5s", kde_output_device_v2_types + 0 },
+ { "brightness_metadata", "6uuu", kde_output_device_v2_types + 0 },
+ { "brightness_overrides", "6iii", kde_output_device_v2_types + 0 },
+ { "sdr_gamut_wideness", "6u", kde_output_device_v2_types + 0 },
+ { "color_profile_source", "7u", kde_output_device_v2_types + 0 },
+ { "brightness", "8u", kde_output_device_v2_types + 0 },
+ { "color_power_tradeoff", "10u", kde_output_device_v2_types + 0 },
+ { "dimming", "11u", kde_output_device_v2_types + 0 },
+ { "replication_source", "13s", kde_output_device_v2_types + 0 },
+ { "ddc_ci_allowed", "14u", kde_output_device_v2_types + 0 },
+ { "max_bits_per_color", "15u", kde_output_device_v2_types + 0 },
+ { "max_bits_per_color_range", "15uu", kde_output_device_v2_types + 0 },
+ { "automatic_max_bits_per_color_limit", "15u", kde_output_device_v2_types + 0 },
+ { "edr_policy", "16u", kde_output_device_v2_types + 0 },
+ { "sharpness", "17u", kde_output_device_v2_types + 0 },
+ { "priority", "18u", kde_output_device_v2_types + 0 },
+ { "auto_brightness", "20u", kde_output_device_v2_types + 0 },
+ { "removed", "21", kde_output_device_v2_types + 0 },
+ { "hdr_icc_profile_path", "22s", kde_output_device_v2_types + 0 },
+ { "hdr_color_profile_source", "22u", kde_output_device_v2_types + 0 },
+ { "abm_level", "23u", kde_output_device_v2_types + 0 },
+};
+
+WL_EXPORT const struct wl_interface kde_output_device_v2_interface = {
+ "kde_output_device_v2",
+ 23,
+ 1,
+ kde_output_device_v2_requests,
+ 40,
+ kde_output_device_v2_events,
+};
+
+static const struct wl_message kde_output_device_mode_v2_events[] = {
+ { "size", "ii", kde_output_device_v2_types + 0 },
+ { "refresh", "i", kde_output_device_v2_types + 0 },
+ { "preferred", "", kde_output_device_v2_types + 0 },
+ { "removed", "", kde_output_device_v2_types + 0 },
+ { "flags", "19u", kde_output_device_v2_types + 0 },
+};
+
+WL_EXPORT const struct wl_interface kde_output_device_mode_v2_interface = {
+ "kde_output_device_mode_v2",
+ 22,
+ 0,
+ NULL,
+ 5,
+ kde_output_device_mode_v2_events,
+};
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h b/src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h
new file mode 100644
index 0000000..b9928c3
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h
@@ -0,0 +1,134 @@
+/* Generated by wayland-scanner 1.22.0 */
+
+#ifndef KDE_OUTPUT_ORDER_V1_CLIENT_PROTOCOL_H
+#define KDE_OUTPUT_ORDER_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_kde_output_order_v1 The kde_output_order_v1 protocol
+ * @section page_ifaces_kde_output_order_v1 Interfaces
+ * - @subpage page_iface_kde_output_order_v1 - announce order of outputs
+ * @section page_copyright_kde_output_order_v1 Copyright
+ * <pre>
+ *
+ * SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
+ *
+ * SPDX-License-Identifier: MIT-CMU
+ * </pre>
+ */
+struct kde_output_order_v1;
+
+#ifndef KDE_OUTPUT_ORDER_V1_INTERFACE
+ #define KDE_OUTPUT_ORDER_V1_INTERFACE
+/**
+ * @page page_iface_kde_output_order_v1 kde_output_order_v1
+ * @section page_iface_kde_output_order_v1_desc Description
+ *
+ * Announce the order in which desktop environment components should be placed on outputs.
+ * The compositor will send the list of outputs when the global is bound and whenever there is a change.
+ * @section page_iface_kde_output_order_v1_api API
+ * See @ref iface_kde_output_order_v1.
+ */
+/**
+ * @defgroup iface_kde_output_order_v1 The kde_output_order_v1 interface
+ *
+ * Announce the order in which desktop environment components should be placed on outputs.
+ * The compositor will send the list of outputs when the global is bound and whenever there is a change.
+ */
+extern const struct wl_interface kde_output_order_v1_interface;
+#endif
+
+/**
+ * @ingroup iface_kde_output_order_v1
+ * @struct kde_output_order_v1_listener
+ */
+struct kde_output_order_v1_listener {
+ /**
+ * output name
+ *
+ * Specifies the output identified by their wl_output.name.
+ * @param output_name the name of the output
+ */
+ void (*output)(void* data,
+ struct kde_output_order_v1* kde_output_order_v1,
+ const char* output_name);
+ /**
+ * done
+ *
+ * Specifies that the output list is complete. On the next output
+ * event, a new list begins.
+ */
+ void (*done)(void* data,
+ struct kde_output_order_v1* kde_output_order_v1);
+};
+
+/**
+ * @ingroup iface_kde_output_order_v1
+ */
+static inline int
+kde_output_order_v1_add_listener(struct kde_output_order_v1* kde_output_order_v1,
+ const struct kde_output_order_v1_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) kde_output_order_v1,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define KDE_OUTPUT_ORDER_V1_DESTROY 0
+
+/**
+ * @ingroup iface_kde_output_order_v1
+ */
+#define KDE_OUTPUT_ORDER_V1_OUTPUT_SINCE_VERSION 1
+/**
+ * @ingroup iface_kde_output_order_v1
+ */
+#define KDE_OUTPUT_ORDER_V1_DONE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_kde_output_order_v1
+ */
+#define KDE_OUTPUT_ORDER_V1_DESTROY_SINCE_VERSION 1
+
+// /** @ingroup iface_kde_output_order_v1 */
+// static inline void
+// kde_output_order_v1_set_user_data(struct kde_output_order_v1 *kde_output_order_v1, void *user_data)
+// {
+// wl_proxy_set_user_data((struct wl_proxy *) kde_output_order_v1, user_data);
+// }
+
+// /** @ingroup iface_kde_output_order_v1 */
+// static inline void *
+// kde_output_order_v1_get_user_data(struct kde_output_order_v1 *kde_output_order_v1)
+// {
+// return wl_proxy_get_user_data((struct wl_proxy *) kde_output_order_v1);
+// }
+
+// static inline uint32_t
+// kde_output_order_v1_get_version(struct kde_output_order_v1 *kde_output_order_v1)
+// {
+// return wl_proxy_get_version((struct wl_proxy *) kde_output_order_v1);
+// }
+
+// /**
+// * @ingroup iface_kde_output_order_v1
+// */
+// static inline void
+// kde_output_order_v1_destroy(struct kde_output_order_v1 *kde_output_order_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) kde_output_order_v1,
+// KDE_OUTPUT_ORDER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) kde_output_order_v1), WL_MARSHAL_FLAG_DESTROY);
+// }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c b/src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c
new file mode 100644
index 0000000..00db32a
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c
@@ -0,0 +1,37 @@
+#ifdef FF_HAVE_WAYLAND
+
+/* Generated by wayland-scanner 1.22.0 */
+
+/*
+ * SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
+ *
+ * SPDX-License-Identifier: MIT-CMU
+ */
+
+ #include <stdlib.h>
+ #include <stdint.h>
+ #include "wayland-util.h"
+
+static const struct wl_interface* kde_output_order_v1_types[] = {
+ NULL,
+};
+
+static const struct wl_message kde_output_order_v1_requests[] = {
+ { "destroy", "", kde_output_order_v1_types + 0 },
+};
+
+static const struct wl_message kde_output_order_v1_events[] = {
+ { "output", "s", kde_output_order_v1_types + 0 },
+ { "done", "", kde_output_order_v1_types + 0 },
+};
+
+WL_EXPORT const struct wl_interface kde_output_order_v1_interface = {
+ "kde_output_order_v1",
+ 1,
+ 1,
+ kde_output_order_v1_requests,
+ 2,
+ kde_output_order_v1_events,
+};
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c
new file mode 100644
index 0000000..90d5770
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/kde-output.c
@@ -0,0 +1,300 @@
+#ifdef FF_HAVE_WAYLAND
+
+ #include "wayland.h"
+ #include "kde-output-device-v2-client-protocol.h"
+ #include "kde-output-order-v1-client-protocol.h"
+ #include "common/edidHelper.h"
+ #include "common/base64.h"
+
+typedef struct WaylandKdeMode {
+ int32_t width;
+ int32_t height;
+ int32_t refreshRate;
+ bool preferred;
+ struct kde_output_device_mode_v2* pMode;
+} WaylandKdeMode;
+
+static void waylandKdeModeSizeListener(void* data, FF_A_UNUSED struct kde_output_device_mode_v2* _, int32_t width, int32_t height) {
+ WaylandKdeMode* mode = (WaylandKdeMode*) data;
+ mode->width = width;
+ mode->height = height;
+}
+
+static void waylandKdeModeRefreshListener(void* data, FF_A_UNUSED struct kde_output_device_mode_v2* _, int32_t rate) {
+ WaylandKdeMode* mode = (WaylandKdeMode*) data;
+ mode->refreshRate = rate;
+}
+
+static void waylandKdeModePreferredListener(void* data, FF_A_UNUSED struct kde_output_device_mode_v2* _) {
+ WaylandKdeMode* mode = (WaylandKdeMode*) data;
+ mode->preferred = true;
+}
+
+static const struct kde_output_device_mode_v2_listener modeListener = {
+ .size = waylandKdeModeSizeListener,
+ .refresh = waylandKdeModeRefreshListener,
+ .preferred = waylandKdeModePreferredListener,
+ .removed = (void*) stubListener,
+ .flags = (void*) stubListener,
+};
+
+static void waylandKdeModeListener(void* data, FF_A_UNUSED struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ if (!wldata->internal) {
+ return;
+ }
+
+ WaylandKdeMode* newMode = FF_LIST_ADD(WaylandKdeMode, *(FFlist*) wldata->internal);
+ *newMode = (WaylandKdeMode) { .pMode = mode };
+
+ // Strangely, the listener is called only in this function, but not in `waylandKdeCurrentModeListener`
+ wldata->parent->ffwl_proxy_add_listener((struct wl_proxy*) mode, (void (**)(void)) &modeListener, newMode);
+}
+
+static void waylandKdeCurrentModeListener(void* data, FF_A_UNUSED struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) {
+ // waylandKdeModeListener is always run before this
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ if (!wldata->internal) {
+ return;
+ }
+
+ int set = 0;
+ FF_LIST_FOR_EACH (WaylandKdeMode, m, *(FFlist*) wldata->internal) {
+ if (m->pMode == mode) {
+ wldata->width = m->width;
+ wldata->height = m->height;
+ wldata->refreshRate = m->refreshRate;
+ if (++set == 2) {
+ break;
+ }
+ }
+ if (m->preferred) {
+ wldata->preferredWidth = m->width;
+ wldata->preferredHeight = m->height;
+ wldata->preferredRefreshRate = m->refreshRate;
+ if (++set == 2) {
+ break;
+ }
+ }
+ }
+}
+
+static void waylandKdeScaleListener(void* data, FF_A_UNUSED struct kde_output_device_v2* _, wl_fixed_t scale) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96;
+}
+
+static void waylandKdeEdidListener(void* data, FF_A_UNUSED struct kde_output_device_v2* _, const char* raw) {
+ if (!*raw) {
+ return;
+ }
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ FF_STRBUF_AUTO_DESTROY b64 = ffStrbufCreateStatic(raw);
+ FF_STRBUF_AUTO_DESTROY edid = ffBase64DecodeStrbuf(&b64);
+ if (edid.length < 128) {
+ return;
+ }
+ ffEdidGetName((const uint8_t*) edid.chars, &wldata->edidName);
+ wldata->hdrSupported = ffEdidGetHdrCompatible((const uint8_t*) edid.chars, edid.length);
+ ffEdidGetSerialAndManufactureDate((const uint8_t*) edid.chars, &wldata->serial, &wldata->myear, &wldata->mweek);
+ wldata->hdrInfoAvailable = true;
+}
+
+static void waylandKdeEnabledListener(void* data, FF_A_UNUSED struct kde_output_device_v2* _, int32_t enabled) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ if (!enabled) {
+ wldata->internal = NULL;
+ }
+}
+
+static void waylandKdeGeometryListener(void* data,
+ FF_A_UNUSED struct kde_output_device_v2* kde_output_device_v2,
+ FF_A_UNUSED int32_t x,
+ FF_A_UNUSED int32_t y,
+ int32_t physical_width,
+ int32_t physical_height,
+ FF_A_UNUSED int32_t subpixel,
+ FF_A_UNUSED const char* make,
+ FF_A_UNUSED const char* model,
+ int32_t transform) {
+ WaylandDisplay* display = data;
+ display->physicalWidth = physical_width;
+ display->physicalHeight = physical_height;
+ display->transform = (enum wl_output_transform) transform;
+}
+
+static void waylandKdeNameListener(void* data, FF_A_UNUSED struct kde_output_device_v2* kde_output_device_v2, const char* name) {
+ WaylandDisplay* display = data;
+ display->type = ffdsGetDisplayType(name);
+ // As display->id is used as an internal identifier, we don't need it to be NUL terminated
+ strncpy((char*) &display->id, name, sizeof(display->id));
+
+ ffStrbufAppendS(&display->name, name);
+}
+
+static void waylandKdeHdrListener(void* data, FF_A_UNUSED struct kde_output_device_v2* kde_output_device_v2, uint32_t hdr_enabled) {
+ WaylandDisplay* display = data;
+ display->hdrEnabled = !!hdr_enabled;
+}
+
+static void waylandKdeMaxBitsPerColorListener(void* data, FF_A_UNUSED struct kde_output_device_v2* kde_output_device_v2, uint32_t max_bpc) {
+ WaylandDisplay* display = data;
+ display->bitDepth = (uint8_t) max_bpc;
+}
+
+static struct kde_output_device_v2_listener outputListener = {
+ .geometry = waylandKdeGeometryListener,
+ .current_mode = waylandKdeCurrentModeListener,
+ .mode = waylandKdeModeListener,
+ .done = (void*) stubListener,
+ .scale = waylandKdeScaleListener,
+ .edid = waylandKdeEdidListener,
+ .enabled = waylandKdeEnabledListener,
+ .uuid = (void*) stubListener,
+ .serial_number = (void*) stubListener,
+ .eisa_id = (void*) stubListener,
+ .capabilities = (void*) stubListener,
+ .overscan = (void*) stubListener,
+ .vrr_policy = (void*) stubListener,
+ .rgb_range = (void*) stubListener,
+ .name = waylandKdeNameListener,
+ .high_dynamic_range = waylandKdeHdrListener,
+ .sdr_brightness = (void*) stubListener,
+ .wide_color_gamut = (void*) stubListener,
+ .auto_rotate_policy = (void*) stubListener,
+ .icc_profile_path = (void*) stubListener,
+ .brightness_metadata = (void*) stubListener,
+ .brightness_overrides = (void*) stubListener,
+ .sdr_gamut_wideness = (void*) stubListener,
+ .color_profile_source = (void*) stubListener,
+ .brightness = (void*) stubListener,
+ .color_power_tradeoff = (void*) stubListener,
+ .dimming = (void*) stubListener,
+ .replication_source = (void*) stubListener,
+ .ddc_ci_allowed = (void*) stubListener,
+ .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener,
+ .max_bits_per_color_range = (void*) stubListener,
+ .automatic_max_bits_per_color_limit = (void*) stubListener,
+ .edr_policy = (void*) stubListener,
+};
+
+const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
+ uint32_t bindVersion = min(version, KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_SINCE_VERSION);
+ struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_v2_interface, bindVersion, name, kde_output_device_v2_interface.name, bindVersion, NULL);
+ if (output == NULL) {
+ return "Failed to create kde_output_device_v2";
+ }
+
+ FF_LIST_AUTO_DESTROY modes = ffListCreate();
+ WaylandDisplay display = {
+ .parent = wldata,
+ .transform = WL_OUTPUT_TRANSFORM_NORMAL,
+ .type = FF_DISPLAY_TYPE_UNKNOWN,
+ .name = ffStrbufCreate(),
+ .description = ffStrbufCreate(),
+ .edidName = ffStrbufCreate(),
+ .internal = &modes,
+ };
+
+ if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, &display) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to add listener to kde_output_device_v2";
+ }
+
+ if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to roundtrip kde_output_device_v2";
+ }
+ // Destroy any mode proxies that were created during the listeners.
+ // wl proxies created for modes are not automatically freed by destroying
+ // the parent output proxy, so destroy them explicitly to avoid leaks.
+ FF_LIST_FOR_EACH (WaylandKdeMode, m, modes) {
+ if (m->pMode) {
+ wldata->ffwl_proxy_destroy((struct wl_proxy*) m->pMode);
+ m->pMode = NULL;
+ }
+ }
+
+ wldata->ffwl_proxy_destroy(output);
+
+ if (display.width <= 0 || display.height <= 0 || !display.internal) {
+ return "Failed to get display information from kde_output_device_v2";
+ }
+
+ uint32_t rotation = ffWaylandHandleRotation(&display);
+
+ FFDisplayResult* item = ffdsAppendDisplay(wldata->result,
+ (uint32_t) display.width,
+ (uint32_t) display.height,
+ display.refreshRate / 1000.0,
+ display.dpi,
+ (uint32_t) display.preferredWidth,
+ (uint32_t) display.preferredHeight,
+ display.preferredRefreshRate / 1000.0,
+ rotation,
+ display.edidName.length
+ ? &display.edidName
+ : &display.name,
+ display.type,
+ false,
+ display.id,
+ (uint32_t) display.physicalWidth,
+ (uint32_t) display.physicalHeight,
+ "wayland-kde");
+ if (item) {
+ if (display.hdrEnabled) {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
+ } else if (display.hdrSupported) {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
+ } else if (display.hdrInfoAvailable) {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ } else {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
+ }
+
+ item->manufactureYear = display.myear;
+ item->manufactureWeek = display.mweek;
+ item->serial = display.serial;
+ item->bitDepth = display.bitDepth;
+ }
+
+ ffStrbufDestroy(&display.description);
+ ffStrbufDestroy(&display.name);
+ ffStrbufDestroy(&display.edidName);
+
+ return NULL;
+}
+
+static void waylandKdeOutputOrderListener(void* data, FF_A_UNUSED struct kde_output_order_v1* _, const char* output_name) {
+ uint64_t* id = (uint64_t*) data;
+ if (*id == 0) {
+ *id = ffWaylandGenerateIdFromName(output_name);
+ }
+}
+
+static const struct kde_output_order_v1_listener orderListener = {
+ .output = waylandKdeOutputOrderListener,
+ .done = (void*) stubListener,
+};
+
+const char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
+ uint32_t bindVersion = min(version, KDE_OUTPUT_ORDER_V1_OUTPUT_SINCE_VERSION);
+ struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_order_v1_interface, bindVersion, name, kde_output_order_v1_interface.name, bindVersion, NULL);
+ if (output == NULL) {
+ return "Failed to create kde_output_order_v1";
+ }
+
+ if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &orderListener, &wldata->primaryDisplayId) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to add listener to kde_output_order_v1";
+ }
+ if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to roundtrip kde_output_order_v1";
+ }
+ wldata->ffwl_proxy_destroy(output);
+
+ return NULL;
+}
+
+#endif
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
diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h
new file mode 100644
index 0000000..b103ab9
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/wayland.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#ifdef FF_HAVE_WAYLAND
+
+ #include "common/library.h"
+ #include "common/strutil.h"
+
+ #include <wayland-client.h>
+
+ #include "../displayserver_linux.h"
+
+static inline uint32_t min(uint32_t a, uint32_t b) {
+ return a < b ? a : b;
+}
+
+typedef enum FF_A_PACKED WaylandProtocolType {
+ FF_WAYLAND_PROTOCOL_TYPE_NONE,
+ FF_WAYLAND_PROTOCOL_TYPE_GLOBAL,
+ FF_WAYLAND_PROTOCOL_TYPE_ZWLR,
+ FF_WAYLAND_PROTOCOL_TYPE_KDE,
+} WaylandProtocolType;
+
+typedef struct WaylandData {
+ FFDisplayServerResult* result;
+ FF_LIBRARY_SYMBOL(wl_proxy_marshal_constructor_versioned)
+ FF_LIBRARY_SYMBOL(wl_proxy_add_listener)
+ FF_LIBRARY_SYMBOL(wl_proxy_destroy)
+ FF_LIBRARY_SYMBOL(wl_display_roundtrip)
+ struct wl_display* display;
+ const struct wl_interface* ffwl_output_interface;
+ WaylandProtocolType protocolType;
+ uint64_t primaryDisplayId;
+ struct wl_proxy* zxdgOutputManager;
+} WaylandData;
+
+typedef struct WaylandDisplay {
+ WaylandData* parent;
+ void* internal;
+ int32_t width;
+ int32_t height;
+ int32_t refreshRate;
+ int32_t preferredWidth;
+ int32_t preferredHeight;
+ int32_t preferredRefreshRate;
+ int32_t physicalWidth;
+ int32_t physicalHeight;
+ uint32_t dpi;
+ enum wl_output_transform transform;
+ FFDisplayType type;
+ FFstrbuf name;
+ FFstrbuf description;
+ FFstrbuf edidName;
+ uint64_t id;
+ bool hdrInfoAvailable;
+ bool hdrSupported;
+ bool hdrEnabled;
+ uint16_t myear;
+ uint16_t mweek;
+ uint32_t serial;
+ uint8_t bitDepth;
+} WaylandDisplay;
+
+inline static void stubListener(void* data, ...) {
+ (void) data;
+}
+
+inline static uint64_t ffWaylandGenerateIdFromName(const char* name) {
+ uint64_t id = 0;
+ size_t len = strlen(name);
+ if (len > sizeof(id)) {
+ memcpy(&id, name + (len - sizeof(id)), sizeof(id)); // copy the last 8 bytes
+ } else if (len > 0) {
+ memcpy(&id, name, len);
+ }
+ return id;
+}
+
+void ffWaylandOutputNameListener(void* data, FF_A_UNUSED void* output, const char* name);
+void ffWaylandOutputDescriptionListener(void* data, FF_A_UNUSED void* output, const char* description);
+// Modifies content of display. Don't call this function when calling ffdsAppendDisplay
+uint32_t ffWaylandHandleRotation(WaylandDisplay* display);
+
+const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
+const char* ffWaylandHandleZwlrOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
+const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
+const char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
+const char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h
new file mode 100644
index 0000000..2b0584d
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h
@@ -0,0 +1,1292 @@
+/* Generated by wayland-scanner 1.24.0 */
+
+#ifndef WLR_OUTPUT_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define WLR_OUTPUT_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <wayland-client.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_wlr_output_management_unstable_v1 The wlr_output_management_unstable_v1 protocol
+ * protocol to configure output devices
+ *
+ * @section page_desc_wlr_output_management_unstable_v1 Description
+ *
+ * This protocol exposes interfaces to obtain and modify output device
+ * configuration.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ *
+ * @section page_ifaces_wlr_output_management_unstable_v1 Interfaces
+ * - @subpage page_iface_zwlr_output_manager_v1 - output device configuration manager
+ * - @subpage page_iface_zwlr_output_head_v1 - output device
+ * - @subpage page_iface_zwlr_output_mode_v1 - output mode
+ * - @subpage page_iface_zwlr_output_configuration_v1 - output configuration
+ * - @subpage page_iface_zwlr_output_configuration_head_v1 - head configuration
+ * @section page_copyright_wlr_output_management_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2019 Purism SPC
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * </pre>
+ */
+struct zwlr_output_configuration_head_v1;
+struct zwlr_output_configuration_v1;
+struct zwlr_output_head_v1;
+struct zwlr_output_manager_v1;
+struct zwlr_output_mode_v1;
+
+#ifndef ZWLR_OUTPUT_MANAGER_V1_INTERFACE
+ #define ZWLR_OUTPUT_MANAGER_V1_INTERFACE
+/**
+ * @page page_iface_zwlr_output_manager_v1 zwlr_output_manager_v1
+ * @section page_iface_zwlr_output_manager_v1_desc Description
+ *
+ * This interface is a manager that allows reading and writing the current
+ * output device configuration.
+ *
+ * Output devices that display pixels (e.g. a physical monitor or a virtual
+ * output in a window) are represented as heads. Heads cannot be created nor
+ * destroyed by the client, but they can be enabled or disabled and their
+ * properties can be changed. Each head may have one or more available modes.
+ *
+ * Whenever a head appears (e.g. a monitor is plugged in), it will be
+ * advertised via the head event. Immediately after the output manager is
+ * bound, all current heads are advertised.
+ *
+ * Whenever a head's properties change, the relevant wlr_output_head events
+ * will be sent. Not all head properties will be sent: only properties that
+ * have changed need to.
+ *
+ * Whenever a head disappears (e.g. a monitor is unplugged), a
+ * wlr_output_head.finished event will be sent.
+ *
+ * After one or more heads appear, change or disappear, the done event will
+ * be sent. It carries a serial which can be used in a create_configuration
+ * request to update heads properties.
+ *
+ * The information obtained from this protocol should only be used for output
+ * configuration purposes. This protocol is not designed to be a generic
+ * output property advertisement protocol for regular clients. Instead,
+ * protocols such as xdg-output should be used.
+ * @section page_iface_zwlr_output_manager_v1_api API
+ * See @ref iface_zwlr_output_manager_v1.
+ */
+/**
+ * @defgroup iface_zwlr_output_manager_v1 The zwlr_output_manager_v1 interface
+ *
+ * This interface is a manager that allows reading and writing the current
+ * output device configuration.
+ *
+ * Output devices that display pixels (e.g. a physical monitor or a virtual
+ * output in a window) are represented as heads. Heads cannot be created nor
+ * destroyed by the client, but they can be enabled or disabled and their
+ * properties can be changed. Each head may have one or more available modes.
+ *
+ * Whenever a head appears (e.g. a monitor is plugged in), it will be
+ * advertised via the head event. Immediately after the output manager is
+ * bound, all current heads are advertised.
+ *
+ * Whenever a head's properties change, the relevant wlr_output_head events
+ * will be sent. Not all head properties will be sent: only properties that
+ * have changed need to.
+ *
+ * Whenever a head disappears (e.g. a monitor is unplugged), a
+ * wlr_output_head.finished event will be sent.
+ *
+ * After one or more heads appear, change or disappear, the done event will
+ * be sent. It carries a serial which can be used in a create_configuration
+ * request to update heads properties.
+ *
+ * The information obtained from this protocol should only be used for output
+ * configuration purposes. This protocol is not designed to be a generic
+ * output property advertisement protocol for regular clients. Instead,
+ * protocols such as xdg-output should be used.
+ */
+extern const struct wl_interface zwlr_output_manager_v1_interface;
+#endif
+#ifndef ZWLR_OUTPUT_HEAD_V1_INTERFACE
+ #define ZWLR_OUTPUT_HEAD_V1_INTERFACE
+/**
+ * @page page_iface_zwlr_output_head_v1 zwlr_output_head_v1
+ * @section page_iface_zwlr_output_head_v1_desc Description
+ *
+ * A head is an output device. The difference between a wl_output object and
+ * a head is that heads are advertised even if they are turned off. A head
+ * object only advertises properties and cannot be used directly to change
+ * them.
+ *
+ * A head has some read-only properties: modes, name, description and
+ * physical_size. These cannot be changed by clients.
+ *
+ * Other properties can be updated via a wlr_output_configuration object.
+ *
+ * Properties sent via this interface are applied atomically via the
+ * wlr_output_manager.done event. No guarantees are made regarding the order
+ * in which properties are sent.
+ * @section page_iface_zwlr_output_head_v1_api API
+ * See @ref iface_zwlr_output_head_v1.
+ */
+/**
+ * @defgroup iface_zwlr_output_head_v1 The zwlr_output_head_v1 interface
+ *
+ * A head is an output device. The difference between a wl_output object and
+ * a head is that heads are advertised even if they are turned off. A head
+ * object only advertises properties and cannot be used directly to change
+ * them.
+ *
+ * A head has some read-only properties: modes, name, description and
+ * physical_size. These cannot be changed by clients.
+ *
+ * Other properties can be updated via a wlr_output_configuration object.
+ *
+ * Properties sent via this interface are applied atomically via the
+ * wlr_output_manager.done event. No guarantees are made regarding the order
+ * in which properties are sent.
+ */
+extern const struct wl_interface zwlr_output_head_v1_interface;
+#endif
+#ifndef ZWLR_OUTPUT_MODE_V1_INTERFACE
+ #define ZWLR_OUTPUT_MODE_V1_INTERFACE
+/**
+ * @page page_iface_zwlr_output_mode_v1 zwlr_output_mode_v1
+ * @section page_iface_zwlr_output_mode_v1_desc Description
+ *
+ * This object describes an output mode.
+ *
+ * Some heads don't support output modes, in which case modes won't be
+ * advertised.
+ *
+ * Properties sent via this interface are applied atomically via the
+ * wlr_output_manager.done event. No guarantees are made regarding the order
+ * in which properties are sent.
+ * @section page_iface_zwlr_output_mode_v1_api API
+ * See @ref iface_zwlr_output_mode_v1.
+ */
+/**
+ * @defgroup iface_zwlr_output_mode_v1 The zwlr_output_mode_v1 interface
+ *
+ * This object describes an output mode.
+ *
+ * Some heads don't support output modes, in which case modes won't be
+ * advertised.
+ *
+ * Properties sent via this interface are applied atomically via the
+ * wlr_output_manager.done event. No guarantees are made regarding the order
+ * in which properties are sent.
+ */
+extern const struct wl_interface zwlr_output_mode_v1_interface;
+#endif
+#ifndef ZWLR_OUTPUT_CONFIGURATION_V1_INTERFACE
+ #define ZWLR_OUTPUT_CONFIGURATION_V1_INTERFACE
+/**
+ * @page page_iface_zwlr_output_configuration_v1 zwlr_output_configuration_v1
+ * @section page_iface_zwlr_output_configuration_v1_desc Description
+ *
+ * This object is used by the client to describe a full output configuration.
+ *
+ * First, the client needs to setup the output configuration. Each head can
+ * be either enabled (and configured) or disabled. It is a protocol error to
+ * send two enable_head or disable_head requests with the same head. It is a
+ * protocol error to omit a head in a configuration.
+ *
+ * Then, the client can apply or test the configuration. The compositor will
+ * then reply with a succeeded, failed or cancelled event. Finally the client
+ * should destroy the configuration object.
+ * @section page_iface_zwlr_output_configuration_v1_api API
+ * See @ref iface_zwlr_output_configuration_v1.
+ */
+/**
+ * @defgroup iface_zwlr_output_configuration_v1 The zwlr_output_configuration_v1 interface
+ *
+ * This object is used by the client to describe a full output configuration.
+ *
+ * First, the client needs to setup the output configuration. Each head can
+ * be either enabled (and configured) or disabled. It is a protocol error to
+ * send two enable_head or disable_head requests with the same head. It is a
+ * protocol error to omit a head in a configuration.
+ *
+ * Then, the client can apply or test the configuration. The compositor will
+ * then reply with a succeeded, failed or cancelled event. Finally the client
+ * should destroy the configuration object.
+ */
+extern const struct wl_interface zwlr_output_configuration_v1_interface;
+#endif
+#ifndef ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_INTERFACE
+ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_INTERFACE
+/**
+ * @page page_iface_zwlr_output_configuration_head_v1 zwlr_output_configuration_head_v1
+ * @section page_iface_zwlr_output_configuration_head_v1_desc Description
+ *
+ * This object is used by the client to update a single head's configuration.
+ *
+ * It is a protocol error to set the same property twice.
+ * @section page_iface_zwlr_output_configuration_head_v1_api API
+ * See @ref iface_zwlr_output_configuration_head_v1.
+ */
+/**
+ * @defgroup iface_zwlr_output_configuration_head_v1 The zwlr_output_configuration_head_v1 interface
+ *
+ * This object is used by the client to update a single head's configuration.
+ *
+ * It is a protocol error to set the same property twice.
+ */
+extern const struct wl_interface zwlr_output_configuration_head_v1_interface;
+#endif
+
+/**
+ * @ingroup iface_zwlr_output_manager_v1
+ * @struct zwlr_output_manager_v1_listener
+ */
+struct zwlr_output_manager_v1_listener {
+ /**
+ * introduce a new head
+ *
+ * This event introduces a new head. This happens whenever a new
+ * head appears (e.g. a monitor is plugged in) or after the output
+ * manager is bound.
+ */
+ void (*head)(void* data,
+ struct zwlr_output_manager_v1* zwlr_output_manager_v1,
+ struct zwlr_output_head_v1* head);
+ /**
+ * sent all information about current configuration
+ *
+ * This event is sent after all information has been sent after
+ * binding to the output manager object and after any subsequent
+ * changes. This applies to child head and mode objects as well. In
+ * other words, this event is sent whenever a head or mode is
+ * created or destroyed and whenever one of their properties has
+ * been changed. Not all state is re-sent each time the current
+ * configuration changes: only the actual changes are sent.
+ *
+ * This allows changes to the output configuration to be seen as
+ * atomic, even if they happen via multiple events.
+ *
+ * A serial is sent to be used in a future create_configuration
+ * request.
+ * @param serial current configuration serial
+ */
+ void (*done)(void* data,
+ struct zwlr_output_manager_v1* zwlr_output_manager_v1,
+ uint32_t serial);
+ /**
+ * the compositor has finished with the manager
+ *
+ * This event indicates that the compositor is done sending
+ * manager events. The compositor will destroy the object
+ * immediately after sending this event, so it will become invalid
+ * and the client should release any resources associated with it.
+ */
+ void (*finished)(void* data,
+ struct zwlr_output_manager_v1* zwlr_output_manager_v1);
+};
+
+/**
+ * @ingroup iface_zwlr_output_manager_v1
+ */
+static inline int
+zwlr_output_manager_v1_add_listener(struct zwlr_output_manager_v1* zwlr_output_manager_v1,
+ const struct zwlr_output_manager_v1_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) zwlr_output_manager_v1,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION 0
+#define ZWLR_OUTPUT_MANAGER_V1_STOP 1
+
+/**
+ * @ingroup iface_zwlr_output_manager_v1
+ */
+#define ZWLR_OUTPUT_MANAGER_V1_HEAD_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_manager_v1
+ */
+#define ZWLR_OUTPUT_MANAGER_V1_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_manager_v1
+ */
+#define ZWLR_OUTPUT_MANAGER_V1_FINISHED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwlr_output_manager_v1
+ */
+#define ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_manager_v1
+ */
+#define ZWLR_OUTPUT_MANAGER_V1_STOP_SINCE_VERSION 1
+
+/** @ingroup iface_zwlr_output_manager_v1 */
+static inline void
+zwlr_output_manager_v1_set_user_data(struct zwlr_output_manager_v1* zwlr_output_manager_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) zwlr_output_manager_v1, user_data);
+}
+
+/** @ingroup iface_zwlr_output_manager_v1 */
+static inline void*
+zwlr_output_manager_v1_get_user_data(struct zwlr_output_manager_v1* zwlr_output_manager_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*) zwlr_output_manager_v1);
+}
+
+static inline uint32_t
+zwlr_output_manager_v1_get_version(struct zwlr_output_manager_v1* zwlr_output_manager_v1) {
+ return wl_proxy_get_version((struct wl_proxy*) zwlr_output_manager_v1);
+}
+
+/** @ingroup iface_zwlr_output_manager_v1 */
+static inline void
+zwlr_output_manager_v1_destroy(struct zwlr_output_manager_v1* zwlr_output_manager_v1) {
+ wl_proxy_destroy((struct wl_proxy*) zwlr_output_manager_v1);
+}
+
+// /**
+// * @ingroup iface_zwlr_output_manager_v1
+// *
+// * Create a new output configuration object. This allows to update head
+// * properties.
+// */
+// static inline struct zwlr_output_configuration_v1 *
+// zwlr_output_manager_v1_create_configuration(struct zwlr_output_manager_v1 *zwlr_output_manager_v1, uint32_t serial)
+// {
+// struct wl_proxy *id;
+
+// id = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_manager_v1,
+// ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION, &zwlr_output_configuration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1), 0, NULL, serial);
+
+// return (struct zwlr_output_configuration_v1 *) id;
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_manager_v1
+// *
+// * Indicates the client no longer wishes to receive events for output
+// * configuration changes. However the compositor may emit further events,
+// * until the finished event is emitted.
+// *
+// * The client must not send any more requests after this one.
+// */
+// static inline void
+// zwlr_output_manager_v1_stop(struct zwlr_output_manager_v1 *zwlr_output_manager_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_manager_v1,
+// ZWLR_OUTPUT_MANAGER_V1_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1), 0);
+// }
+
+#ifndef ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM
+ #define ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM
+enum zwlr_output_head_v1_adaptive_sync_state {
+ /**
+ * adaptive sync is disabled
+ */
+ ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED = 0,
+ /**
+ * adaptive sync is enabled
+ */
+ ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED = 1,
+};
+#endif /* ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM */
+
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ * @struct zwlr_output_head_v1_listener
+ */
+struct zwlr_output_head_v1_listener {
+ /**
+ * head name
+ *
+ * This event describes the head name.
+ *
+ * The naming convention is compositor defined, but limited to
+ * alphanumeric characters and dashes (-). Each name is unique
+ * among all wlr_output_head objects, but if a wlr_output_head
+ * object is destroyed the same name may be reused later. The names
+ * will also remain consistent across sessions with the same
+ * hardware and software configuration.
+ *
+ * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc.
+ * However, do not assume that the name is a reflection of an
+ * underlying DRM connector, X11 connection, etc.
+ *
+ * If this head matches a wl_output, the wl_output.name event must
+ * report the same name.
+ *
+ * The name event is sent after a wlr_output_head object is
+ * created. This event is only sent once per object, and the name
+ * does not change over the lifetime of the wlr_output_head object.
+ */
+ void (*name)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ const char* name);
+ /**
+ * head description
+ *
+ * This event describes a human-readable description of the head.
+ *
+ * The description is a UTF-8 string with no convention defined for
+ * its contents. Examples might include 'Foocorp 11" Display' or
+ * 'Virtual X11 output via :1'. However, do not assume that the
+ * name is a reflection of the make, model, serial of the
+ * underlying DRM connector or the display name of the underlying
+ * X11 connection, etc.
+ *
+ * If this head matches a wl_output, the wl_output.description
+ * event must report the same name.
+ *
+ * The description event is sent after a wlr_output_head object is
+ * created. This event is only sent once per object, and the
+ * description does not change over the lifetime of the
+ * wlr_output_head object.
+ */
+ void (*description)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ const char* description);
+ /**
+ * head physical size
+ *
+ * This event describes the physical size of the head. This event
+ * is only sent if the head has a physical size (e.g. is not a
+ * projector or a virtual device).
+ *
+ * The physical size event is sent after a wlr_output_head object
+ * is created. This event is only sent once per object, and the
+ * physical size does not change over the lifetime of the
+ * wlr_output_head object.
+ * @param width width in millimeters of the output
+ * @param height height in millimeters of the output
+ */
+ void (*physical_size)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ int32_t width,
+ int32_t height);
+ /**
+ * introduce a mode
+ *
+ * This event introduces a mode for this head. It is sent once
+ * per supported mode.
+ */
+ void (*mode)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ struct zwlr_output_mode_v1* mode);
+ /**
+ * head is enabled or disabled
+ *
+ * This event describes whether the head is enabled. A disabled
+ * head is not mapped to a region of the global compositor space.
+ *
+ * When a head is disabled, some properties (current_mode,
+ * position, transform and scale) are irrelevant.
+ * @param enabled zero if disabled, non-zero if enabled
+ */
+ void (*enabled)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ int32_t enabled);
+ /**
+ * current mode
+ *
+ * This event describes the mode currently in use for this head.
+ * It is only sent if the output is enabled.
+ */
+ void (*current_mode)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ struct zwlr_output_mode_v1* mode);
+ /**
+ * current position
+ *
+ * This events describes the position of the head in the global
+ * compositor space. It is only sent if the output is enabled.
+ * @param x x position within the global compositor space
+ * @param y y position within the global compositor space
+ */
+ void (*position)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ int32_t x,
+ int32_t y);
+ /**
+ * current transformation
+ *
+ * This event describes the transformation currently applied to
+ * the head. It is only sent if the output is enabled.
+ */
+ void (*transform)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ int32_t transform);
+ /**
+ * current scale
+ *
+ * This events describes the scale of the head in the global
+ * compositor space. It is only sent if the output is enabled.
+ */
+ void (*scale)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ wl_fixed_t scale);
+ /**
+ * the head has disappeared
+ *
+ * This event indicates that the head is no longer available. The
+ * head object becomes inert. Clients should send a destroy request
+ * and release any resources associated with it.
+ */
+ void (*finished)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1);
+ /**
+ * head manufacturer
+ *
+ * This event describes the manufacturer of the head.
+ *
+ * Together with the model and serial_number events the purpose is
+ * to allow clients to recognize heads from previous sessions and
+ * for example load head-specific configurations back.
+ *
+ * It is not guaranteed this event will be ever sent. A reason for
+ * that can be that the compositor does not have information about
+ * the make of the head or the definition of a make is not sensible
+ * in the current setup, for example in a virtual session. Clients
+ * can still try to identify the head by available information from
+ * other events but should be aware that there is an increased risk
+ * of false positives.
+ *
+ * If sent, the make event is sent after a wlr_output_head object
+ * is created and only sent once per object. The make does not
+ * change over the lifetime of the wlr_output_head object.
+ *
+ * It is not recommended to display the make string in UI to users.
+ * For that the string provided by the description event should be
+ * preferred.
+ * @since 2
+ */
+ void (*make)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ const char* make);
+ /**
+ * head model
+ *
+ * This event describes the model of the head.
+ *
+ * Together with the make and serial_number events the purpose is
+ * to allow clients to recognize heads from previous sessions and
+ * for example load head-specific configurations back.
+ *
+ * It is not guaranteed this event will be ever sent. A reason for
+ * that can be that the compositor does not have information about
+ * the model of the head or the definition of a model is not
+ * sensible in the current setup, for example in a virtual session.
+ * Clients can still try to identify the head by available
+ * information from other events but should be aware that there is
+ * an increased risk of false positives.
+ *
+ * If sent, the model event is sent after a wlr_output_head object
+ * is created and only sent once per object. The model does not
+ * change over the lifetime of the wlr_output_head object.
+ *
+ * It is not recommended to display the model string in UI to
+ * users. For that the string provided by the description event
+ * should be preferred.
+ * @since 2
+ */
+ void (*model)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ const char* model);
+ /**
+ * head serial number
+ *
+ * This event describes the serial number of the head.
+ *
+ * Together with the make and model events the purpose is to allow
+ * clients to recognize heads from previous sessions and for
+ * example load head- specific configurations back.
+ *
+ * It is not guaranteed this event will be ever sent. A reason for
+ * that can be that the compositor does not have information about
+ * the serial number of the head or the definition of a serial
+ * number is not sensible in the current setup. Clients can still
+ * try to identify the head by available information from other
+ * events but should be aware that there is an increased risk of
+ * false positives.
+ *
+ * If sent, the serial number event is sent after a wlr_output_head
+ * object is created and only sent once per object. The serial
+ * number does not change over the lifetime of the wlr_output_head
+ * object.
+ *
+ * It is not recommended to display the serial_number string in UI
+ * to users. For that the string provided by the description event
+ * should be preferred.
+ * @since 2
+ */
+ void (*serial_number)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ const char* serial_number);
+ /**
+ * current adaptive sync state
+ *
+ * This event describes whether adaptive sync is currently
+ * enabled for the head or not. Adaptive sync is also known as
+ * Variable Refresh Rate or VRR.
+ * @since 4
+ */
+ void (*adaptive_sync)(void* data,
+ struct zwlr_output_head_v1* zwlr_output_head_v1,
+ uint32_t state);
+};
+
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+static inline int
+zwlr_output_head_v1_add_listener(struct zwlr_output_head_v1* zwlr_output_head_v1,
+ const struct zwlr_output_head_v1_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) zwlr_output_head_v1,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define ZWLR_OUTPUT_HEAD_V1_RELEASE 0
+
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_NAME_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_DESCRIPTION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_PHYSICAL_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_ENABLED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_CURRENT_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_POSITION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_TRANSFORM_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_SCALE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_FINISHED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_MAKE_SINCE_VERSION 2
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_MODEL_SINCE_VERSION 2
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_SERIAL_NUMBER_SINCE_VERSION 2
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_SINCE_VERSION 4
+
+/**
+ * @ingroup iface_zwlr_output_head_v1
+ */
+#define ZWLR_OUTPUT_HEAD_V1_RELEASE_SINCE_VERSION 3
+
+/** @ingroup iface_zwlr_output_head_v1 */
+static inline void
+zwlr_output_head_v1_set_user_data(struct zwlr_output_head_v1* zwlr_output_head_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) zwlr_output_head_v1, user_data);
+}
+
+/** @ingroup iface_zwlr_output_head_v1 */
+static inline void*
+zwlr_output_head_v1_get_user_data(struct zwlr_output_head_v1* zwlr_output_head_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*) zwlr_output_head_v1);
+}
+
+static inline uint32_t
+zwlr_output_head_v1_get_version(struct zwlr_output_head_v1* zwlr_output_head_v1) {
+ return wl_proxy_get_version((struct wl_proxy*) zwlr_output_head_v1);
+}
+
+/** @ingroup iface_zwlr_output_head_v1 */
+static inline void
+zwlr_output_head_v1_destroy(struct zwlr_output_head_v1* zwlr_output_head_v1) {
+ wl_proxy_destroy((struct wl_proxy*) zwlr_output_head_v1);
+}
+
+// /**
+// * @ingroup iface_zwlr_output_head_v1
+// *
+// * This request indicates that the client will no longer use this head
+// * object.
+// */
+// static inline void
+// zwlr_output_head_v1_release(struct zwlr_output_head_v1 *zwlr_output_head_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_head_v1,
+// ZWLR_OUTPUT_HEAD_V1_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_head_v1), WL_MARSHAL_FLAG_DESTROY);
+// }
+
+/**
+ * @ingroup iface_zwlr_output_mode_v1
+ * @struct zwlr_output_mode_v1_listener
+ */
+struct zwlr_output_mode_v1_listener {
+ /**
+ * mode size
+ *
+ * This event describes the mode size. The size is given in
+ * physical hardware units of the output device. This is not
+ * necessarily the same as the output size in the global compositor
+ * space. For instance, the output may be scaled or transformed.
+ * @param width width of the mode in hardware units
+ * @param height height of the mode in hardware units
+ */
+ void (*size)(void* data,
+ struct zwlr_output_mode_v1* zwlr_output_mode_v1,
+ int32_t width,
+ int32_t height);
+ /**
+ * mode refresh rate
+ *
+ * This event describes the mode's fixed vertical refresh rate.
+ * It is only sent if the mode has a fixed refresh rate.
+ * @param refresh vertical refresh rate in mHz
+ */
+ void (*refresh)(void* data,
+ struct zwlr_output_mode_v1* zwlr_output_mode_v1,
+ int32_t refresh);
+ /**
+ * mode is preferred
+ *
+ * This event advertises this mode as preferred.
+ */
+ void (*preferred)(void* data,
+ struct zwlr_output_mode_v1* zwlr_output_mode_v1);
+ /**
+ * the mode has disappeared
+ *
+ * This event indicates that the mode is no longer available. The
+ * mode object becomes inert. Clients should send a destroy request
+ * and release any resources associated with it.
+ */
+ void (*finished)(void* data,
+ struct zwlr_output_mode_v1* zwlr_output_mode_v1);
+};
+
+/**
+ * @ingroup iface_zwlr_output_mode_v1
+ */
+static inline int
+zwlr_output_mode_v1_add_listener(struct zwlr_output_mode_v1* zwlr_output_mode_v1,
+ const struct zwlr_output_mode_v1_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) zwlr_output_mode_v1,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define ZWLR_OUTPUT_MODE_V1_RELEASE 0
+
+/**
+ * @ingroup iface_zwlr_output_mode_v1
+ */
+#define ZWLR_OUTPUT_MODE_V1_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_mode_v1
+ */
+#define ZWLR_OUTPUT_MODE_V1_REFRESH_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_mode_v1
+ */
+#define ZWLR_OUTPUT_MODE_V1_PREFERRED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_mode_v1
+ */
+#define ZWLR_OUTPUT_MODE_V1_FINISHED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwlr_output_mode_v1
+ */
+#define ZWLR_OUTPUT_MODE_V1_RELEASE_SINCE_VERSION 3
+
+/** @ingroup iface_zwlr_output_mode_v1 */
+static inline void
+zwlr_output_mode_v1_set_user_data(struct zwlr_output_mode_v1* zwlr_output_mode_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) zwlr_output_mode_v1, user_data);
+}
+
+/** @ingroup iface_zwlr_output_mode_v1 */
+static inline void*
+zwlr_output_mode_v1_get_user_data(struct zwlr_output_mode_v1* zwlr_output_mode_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*) zwlr_output_mode_v1);
+}
+
+static inline uint32_t
+zwlr_output_mode_v1_get_version(struct zwlr_output_mode_v1* zwlr_output_mode_v1) {
+ return wl_proxy_get_version((struct wl_proxy*) zwlr_output_mode_v1);
+}
+
+/** @ingroup iface_zwlr_output_mode_v1 */
+static inline void
+zwlr_output_mode_v1_destroy(struct zwlr_output_mode_v1* zwlr_output_mode_v1) {
+ wl_proxy_destroy((struct wl_proxy*) zwlr_output_mode_v1);
+}
+
+// /**
+// * @ingroup iface_zwlr_output_mode_v1
+// *
+// * This request indicates that the client will no longer use this mode
+// * object.
+// */
+// static inline void
+// zwlr_output_mode_v1_release(struct zwlr_output_mode_v1 *zwlr_output_mode_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_mode_v1,
+// ZWLR_OUTPUT_MODE_V1_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_mode_v1), WL_MARSHAL_FLAG_DESTROY);
+// }
+
+#ifndef ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM
+ #define ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM
+enum zwlr_output_configuration_v1_error {
+ /**
+ * head has been configured twice
+ */
+ ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_CONFIGURED_HEAD = 1,
+ /**
+ * head has not been configured
+ */
+ ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_UNCONFIGURED_HEAD = 2,
+ /**
+ * request sent after configuration has been applied or tested
+ */
+ ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_USED = 3,
+};
+#endif /* ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM */
+
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ * @struct zwlr_output_configuration_v1_listener
+ */
+struct zwlr_output_configuration_v1_listener {
+ /**
+ * configuration changes succeeded
+ *
+ * Sent after the compositor has successfully applied the changes
+ * or tested them.
+ *
+ * Upon receiving this event, the client should destroy this
+ * object.
+ *
+ * If the current configuration has changed, events to describe the
+ * changes will be sent followed by a wlr_output_manager.done
+ * event.
+ */
+ void (*succeeded)(void* data,
+ struct zwlr_output_configuration_v1* zwlr_output_configuration_v1);
+ /**
+ * configuration changes failed
+ *
+ * Sent if the compositor rejects the changes or failed to apply
+ * them. The compositor should revert any changes made by the apply
+ * request that triggered this event.
+ *
+ * Upon receiving this event, the client should destroy this
+ * object.
+ */
+ void (*failed)(void* data,
+ struct zwlr_output_configuration_v1* zwlr_output_configuration_v1);
+ /**
+ * configuration has been cancelled
+ *
+ * Sent if the compositor cancels the configuration because the
+ * state of an output changed and the client has outdated
+ * information (e.g. after an output has been hotplugged).
+ *
+ * The client can create a new configuration with a newer serial
+ * and try again.
+ *
+ * Upon receiving this event, the client should destroy this
+ * object.
+ */
+ void (*cancelled)(void* data,
+ struct zwlr_output_configuration_v1* zwlr_output_configuration_v1);
+};
+
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+static inline int
+zwlr_output_configuration_v1_add_listener(struct zwlr_output_configuration_v1* zwlr_output_configuration_v1,
+ const struct zwlr_output_configuration_v1_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) zwlr_output_configuration_v1,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD 0
+#define ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD 1
+#define ZWLR_OUTPUT_CONFIGURATION_V1_APPLY 2
+#define ZWLR_OUTPUT_CONFIGURATION_V1_TEST 3
+#define ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY 4
+
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_SUCCEEDED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_FAILED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_CANCELLED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_APPLY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_TEST_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwlr_output_configuration_v1 */
+static inline void
+zwlr_output_configuration_v1_set_user_data(struct zwlr_output_configuration_v1* zwlr_output_configuration_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) zwlr_output_configuration_v1, user_data);
+}
+
+/** @ingroup iface_zwlr_output_configuration_v1 */
+static inline void*
+zwlr_output_configuration_v1_get_user_data(struct zwlr_output_configuration_v1* zwlr_output_configuration_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*) zwlr_output_configuration_v1);
+}
+
+static inline uint32_t
+zwlr_output_configuration_v1_get_version(struct zwlr_output_configuration_v1* zwlr_output_configuration_v1) {
+ return wl_proxy_get_version((struct wl_proxy*) zwlr_output_configuration_v1);
+}
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_v1
+// *
+// * Enable a head. This request creates a head configuration object that can
+// * be used to change the head's properties.
+// */
+// static inline struct zwlr_output_configuration_head_v1 *
+// zwlr_output_configuration_v1_enable_head(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, struct zwlr_output_head_v1 *head)
+// {
+// struct wl_proxy *id;
+
+// id = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,
+// ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD, &zwlr_output_configuration_head_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0, NULL, head);
+
+// return (struct zwlr_output_configuration_head_v1 *) id;
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_v1
+// *
+// * Disable a head.
+// */
+// static inline void
+// zwlr_output_configuration_v1_disable_head(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, struct zwlr_output_head_v1 *head)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,
+// ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0, head);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_v1
+// *
+// * Apply the new output configuration.
+// *
+// * In case the configuration is successfully applied, there is no guarantee
+// * that the new output state matches completely the requested
+// * configuration. For instance, a compositor might round the scale if it
+// * doesn't support fractional scaling.
+// *
+// * After this request has been sent, the compositor must respond with an
+// * succeeded, failed or cancelled event. Sending a request that isn't the
+// * destructor is a protocol error.
+// */
+// static inline void
+// zwlr_output_configuration_v1_apply(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,
+// ZWLR_OUTPUT_CONFIGURATION_V1_APPLY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_v1
+// *
+// * Test the new output configuration. The configuration won't be applied,
+// * but will only be validated.
+// *
+// * Even if the compositor succeeds to test a configuration, applying it may
+// * fail.
+// *
+// * After this request has been sent, the compositor must respond with an
+// * succeeded, failed or cancelled event. Sending a request that isn't the
+// * destructor is a protocol error.
+// */
+// static inline void
+// zwlr_output_configuration_v1_test(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,
+// ZWLR_OUTPUT_CONFIGURATION_V1_TEST, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_v1
+// *
+// * Using this request a client can tell the compositor that it is not going
+// * to use the configuration object anymore. Any changes to the outputs
+// * that have not been applied will be discarded.
+// *
+// * This request also destroys wlr_output_configuration_head objects created
+// * via this object.
+// */
+// static inline void
+// zwlr_output_configuration_v1_destroy(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1,
+// ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), WL_MARSHAL_FLAG_DESTROY);
+// }
+
+#ifndef ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM
+ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM
+enum zwlr_output_configuration_head_v1_error {
+ /**
+ * property has already been set
+ */
+ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET = 1,
+ /**
+ * mode doesn't belong to head
+ */
+ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_MODE = 2,
+ /**
+ * mode is invalid
+ */
+ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_CUSTOM_MODE = 3,
+ /**
+ * transform value outside enum
+ */
+ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_TRANSFORM = 4,
+ /**
+ * scale negative or zero
+ */
+ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_SCALE = 5,
+ /**
+ * invalid enum value used in the set_adaptive_sync request
+ * @since 4
+ */
+ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE = 6,
+};
+ /**
+ * @ingroup iface_zwlr_output_configuration_head_v1
+ */
+ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE_SINCE_VERSION 4
+#endif /* ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM */
+
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE 0
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE 1
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION 2
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM 3
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE 4
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC 5
+
+/**
+ * @ingroup iface_zwlr_output_configuration_head_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_head_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_head_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_head_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_head_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwlr_output_configuration_head_v1
+ */
+#define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC_SINCE_VERSION 4
+
+/** @ingroup iface_zwlr_output_configuration_head_v1 */
+static inline void
+zwlr_output_configuration_head_v1_set_user_data(struct zwlr_output_configuration_head_v1* zwlr_output_configuration_head_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*) zwlr_output_configuration_head_v1, user_data);
+}
+
+/** @ingroup iface_zwlr_output_configuration_head_v1 */
+static inline void*
+zwlr_output_configuration_head_v1_get_user_data(struct zwlr_output_configuration_head_v1* zwlr_output_configuration_head_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*) zwlr_output_configuration_head_v1);
+}
+
+static inline uint32_t
+zwlr_output_configuration_head_v1_get_version(struct zwlr_output_configuration_head_v1* zwlr_output_configuration_head_v1) {
+ return wl_proxy_get_version((struct wl_proxy*) zwlr_output_configuration_head_v1);
+}
+
+/** @ingroup iface_zwlr_output_configuration_head_v1 */
+static inline void
+zwlr_output_configuration_head_v1_destroy(struct zwlr_output_configuration_head_v1* zwlr_output_configuration_head_v1) {
+ wl_proxy_destroy((struct wl_proxy*) zwlr_output_configuration_head_v1);
+}
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_head_v1
+// *
+// * This request sets the head's mode.
+// */
+// static inline void
+// zwlr_output_configuration_head_v1_set_mode(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, struct zwlr_output_mode_v1 *mode)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,
+// ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, mode);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_head_v1
+// *
+// * This request assigns a custom mode to the head. The size is given in
+// * physical hardware units of the output device. If set to zero, the
+// * refresh rate is unspecified.
+// *
+// * It is a protocol error to set both a mode and a custom mode.
+// */
+// static inline void
+// zwlr_output_configuration_head_v1_set_custom_mode(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t width, int32_t height, int32_t refresh)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,
+// ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, width, height, refresh);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_head_v1
+// *
+// * This request sets the head's position in the global compositor space.
+// */
+// static inline void
+// zwlr_output_configuration_head_v1_set_position(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t x, int32_t y)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,
+// ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, x, y);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_head_v1
+// *
+// * This request sets the head's transform.
+// */
+// static inline void
+// zwlr_output_configuration_head_v1_set_transform(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t transform)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,
+// ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, transform);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_head_v1
+// *
+// * This request sets the head's scale.
+// */
+// static inline void
+// zwlr_output_configuration_head_v1_set_scale(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, wl_fixed_t scale)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,
+// ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, scale);
+// }
+
+// /**
+// * @ingroup iface_zwlr_output_configuration_head_v1
+// *
+// * This request enables/disables adaptive sync. Adaptive sync is also
+// * known as Variable Refresh Rate or VRR.
+// */
+// static inline void
+// zwlr_output_configuration_head_v1_set_adaptive_sync(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, uint32_t state)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1,
+// ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, state);
+// }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c
new file mode 100644
index 0000000..a231fd4
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c
@@ -0,0 +1,176 @@
+#ifdef FF_HAVE_WAYLAND
+
+/* Generated by wayland-scanner 1.24.0 */
+
+/*
+ * Copyright © 2019 Purism SPC
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ */
+
+ #include <stdbool.h>
+ #include <stdlib.h>
+ #include <stdint.h>
+ #include <wayland-util.h>
+
+ #ifndef __has_attribute
+ #define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+ #endif
+
+ #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+ #define WL_PRIVATE __attribute__((visibility("hidden")))
+ #else
+ #define WL_PRIVATE
+ #endif
+
+extern const struct wl_interface zwlr_output_configuration_head_v1_interface;
+extern const struct wl_interface zwlr_output_configuration_v1_interface;
+extern const struct wl_interface zwlr_output_head_v1_interface;
+extern const struct wl_interface zwlr_output_mode_v1_interface;
+
+static const struct wl_interface* wlr_output_management_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ NULL,
+ &zwlr_output_configuration_v1_interface,
+ NULL,
+ &zwlr_output_head_v1_interface,
+ &zwlr_output_mode_v1_interface,
+ &zwlr_output_mode_v1_interface,
+ &zwlr_output_configuration_head_v1_interface,
+ &zwlr_output_head_v1_interface,
+ &zwlr_output_head_v1_interface,
+ &zwlr_output_mode_v1_interface,
+};
+
+static const struct wl_message zwlr_output_manager_v1_requests[] = {
+ { "create_configuration", "nu", wlr_output_management_unstable_v1_types + 3 },
+ { "stop", "", wlr_output_management_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwlr_output_manager_v1_events[] = {
+ { "head", "n", wlr_output_management_unstable_v1_types + 5 },
+ { "done", "u", wlr_output_management_unstable_v1_types + 0 },
+ { "finished", "", wlr_output_management_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwlr_output_manager_v1_interface = {
+ "zwlr_output_manager_v1",
+ 4,
+ 2,
+ zwlr_output_manager_v1_requests,
+ 3,
+ zwlr_output_manager_v1_events,
+};
+
+static const struct wl_message zwlr_output_head_v1_requests[] = {
+ { "release", "3", wlr_output_management_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwlr_output_head_v1_events[] = {
+ { "name", "s", wlr_output_management_unstable_v1_types + 0 },
+ { "description", "s", wlr_output_management_unstable_v1_types + 0 },
+ { "physical_size", "ii", wlr_output_management_unstable_v1_types + 0 },
+ { "mode", "n", wlr_output_management_unstable_v1_types + 6 },
+ { "enabled", "i", wlr_output_management_unstable_v1_types + 0 },
+ { "current_mode", "o", wlr_output_management_unstable_v1_types + 7 },
+ { "position", "ii", wlr_output_management_unstable_v1_types + 0 },
+ { "transform", "i", wlr_output_management_unstable_v1_types + 0 },
+ { "scale", "f", wlr_output_management_unstable_v1_types + 0 },
+ { "finished", "", wlr_output_management_unstable_v1_types + 0 },
+ { "make", "2s", wlr_output_management_unstable_v1_types + 0 },
+ { "model", "2s", wlr_output_management_unstable_v1_types + 0 },
+ { "serial_number", "2s", wlr_output_management_unstable_v1_types + 0 },
+ { "adaptive_sync", "4u", wlr_output_management_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwlr_output_head_v1_interface = {
+ "zwlr_output_head_v1",
+ 4,
+ 1,
+ zwlr_output_head_v1_requests,
+ 14,
+ zwlr_output_head_v1_events,
+};
+
+static const struct wl_message zwlr_output_mode_v1_requests[] = {
+ { "release", "3", wlr_output_management_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwlr_output_mode_v1_events[] = {
+ { "size", "ii", wlr_output_management_unstable_v1_types + 0 },
+ { "refresh", "i", wlr_output_management_unstable_v1_types + 0 },
+ { "preferred", "", wlr_output_management_unstable_v1_types + 0 },
+ { "finished", "", wlr_output_management_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwlr_output_mode_v1_interface = {
+ "zwlr_output_mode_v1",
+ 3,
+ 1,
+ zwlr_output_mode_v1_requests,
+ 4,
+ zwlr_output_mode_v1_events,
+};
+
+static const struct wl_message zwlr_output_configuration_v1_requests[] = {
+ { "enable_head", "no", wlr_output_management_unstable_v1_types + 8 },
+ { "disable_head", "o", wlr_output_management_unstable_v1_types + 10 },
+ { "apply", "", wlr_output_management_unstable_v1_types + 0 },
+ { "test", "", wlr_output_management_unstable_v1_types + 0 },
+ { "destroy", "", wlr_output_management_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwlr_output_configuration_v1_events[] = {
+ { "succeeded", "", wlr_output_management_unstable_v1_types + 0 },
+ { "failed", "", wlr_output_management_unstable_v1_types + 0 },
+ { "cancelled", "", wlr_output_management_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwlr_output_configuration_v1_interface = {
+ "zwlr_output_configuration_v1",
+ 4,
+ 5,
+ zwlr_output_configuration_v1_requests,
+ 3,
+ zwlr_output_configuration_v1_events,
+};
+
+static const struct wl_message zwlr_output_configuration_head_v1_requests[] = {
+ { "set_mode", "o", wlr_output_management_unstable_v1_types + 11 },
+ { "set_custom_mode", "iii", wlr_output_management_unstable_v1_types + 0 },
+ { "set_position", "ii", wlr_output_management_unstable_v1_types + 0 },
+ { "set_transform", "i", wlr_output_management_unstable_v1_types + 0 },
+ { "set_scale", "f", wlr_output_management_unstable_v1_types + 0 },
+ { "set_adaptive_sync", "4u", wlr_output_management_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwlr_output_configuration_head_v1_interface = {
+ "zwlr_output_configuration_head_v1",
+ 4,
+ 6,
+ zwlr_output_configuration_head_v1_requests,
+ 0,
+ NULL,
+};
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h
new file mode 100644
index 0000000..51784c6
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h
@@ -0,0 +1,414 @@
+/* Generated by wayland-scanner 1.22.0 */
+
+#ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <wayland-client.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol
+ * Protocol to describe output regions
+ *
+ * @section page_desc_xdg_output_unstable_v1 Description
+ *
+ * This protocol aims at describing outputs in a way which is more in line
+ * with the concept of an output on desktop oriented systems.
+ *
+ * Some information are more specific to the concept of an output for
+ * a desktop oriented system and may not make sense in other applications,
+ * such as IVI systems for example.
+ *
+ * Typically, the global compositor space on a desktop system is made of
+ * a contiguous or overlapping set of rectangular regions.
+ *
+ * The logical_position and logical_size events defined in this protocol
+ * might provide information identical to their counterparts already
+ * available from wl_output, in which case the information provided by this
+ * protocol should be preferred to their equivalent in wl_output. The goal is
+ * to move the desktop specific concepts (such as output location within the
+ * global compositor space, etc.) out of the core wl_output protocol.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible
+ * changes may be added together with the corresponding interface
+ * version bump.
+ * Backward incompatible changes are done by bumping the version
+ * number in the protocol and interface names and resetting the
+ * interface version. Once the protocol is to be declared stable,
+ * the 'z' prefix and the version number in the protocol and
+ * interface names are removed and the interface version number is
+ * reset.
+ *
+ * @section page_ifaces_xdg_output_unstable_v1 Interfaces
+ * - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects
+ * - @subpage page_iface_zxdg_output_v1 - compositor logical output region
+ * @section page_copyright_xdg_output_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_output;
+struct zxdg_output_manager_v1;
+struct zxdg_output_v1;
+
+#ifndef ZXDG_OUTPUT_MANAGER_V1_INTERFACE
+ #define ZXDG_OUTPUT_MANAGER_V1_INTERFACE
+/**
+ * @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1
+ * @section page_iface_zxdg_output_manager_v1_desc Description
+ *
+ * A global factory interface for xdg_output objects.
+ * @section page_iface_zxdg_output_manager_v1_api API
+ * See @ref iface_zxdg_output_manager_v1.
+ */
+/**
+ * @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface
+ *
+ * A global factory interface for xdg_output objects.
+ */
+extern const struct wl_interface zxdg_output_manager_v1_interface;
+#endif
+#ifndef ZXDG_OUTPUT_V1_INTERFACE
+ #define ZXDG_OUTPUT_V1_INTERFACE
+/**
+ * @page page_iface_zxdg_output_v1 zxdg_output_v1
+ * @section page_iface_zxdg_output_v1_desc Description
+ *
+ * An xdg_output describes part of the compositor geometry.
+ *
+ * This typically corresponds to a monitor that displays part of the
+ * compositor space.
+ *
+ * For objects version 3 onwards, after all xdg_output properties have been
+ * sent (when the object is created and when properties are updated), a
+ * wl_output.done event is sent. This allows changes to the output
+ * properties to be seen as atomic, even if they happen via multiple events.
+ * @section page_iface_zxdg_output_v1_api API
+ * See @ref iface_zxdg_output_v1.
+ */
+/**
+ * @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface
+ *
+ * An xdg_output describes part of the compositor geometry.
+ *
+ * This typically corresponds to a monitor that displays part of the
+ * compositor space.
+ *
+ * For objects version 3 onwards, after all xdg_output properties have been
+ * sent (when the object is created and when properties are updated), a
+ * wl_output.done event is sent. This allows changes to the output
+ * properties to be seen as atomic, even if they happen via multiple events.
+ */
+extern const struct wl_interface zxdg_output_v1_interface;
+#endif
+
+#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0
+#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1
+
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ */
+#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ */
+#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1
+
+// /** @ingroup iface_zxdg_output_manager_v1 */
+// static inline void
+// zxdg_output_manager_v1_set_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, void *user_data)
+// {
+// wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_manager_v1, user_data);
+// }
+
+// /** @ingroup iface_zxdg_output_manager_v1 */
+// static inline void *
+// zxdg_output_manager_v1_get_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)
+// {
+// return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_manager_v1);
+// }
+
+// static inline uint32_t
+// zxdg_output_manager_v1_get_version(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)
+// {
+// return wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1);
+// }
+
+// /**
+// * @ingroup iface_zxdg_output_manager_v1
+// *
+// * Using this request a client can tell the server that it is not
+// * going to use the xdg_output_manager object anymore.
+// *
+// * Any objects already created through this instance are not affected.
+// */
+// static inline void
+// zxdg_output_manager_v1_destroy(struct zxdg_output_manager_v1 *zxdg_output_manager_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1,
+// ZXDG_OUTPUT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), WL_MARSHAL_FLAG_DESTROY);
+// }
+
+// /**
+// * @ingroup iface_zxdg_output_manager_v1
+// *
+// * This creates a new xdg_output object for the given wl_output.
+// */
+// static inline struct zxdg_output_v1 *
+// zxdg_output_manager_v1_get_xdg_output(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, struct wl_output *output)
+// {
+// struct wl_proxy *id;
+
+// id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1,
+// ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), 0, NULL, output);
+
+// return (struct zxdg_output_v1 *) id;
+// }
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ * @struct zxdg_output_v1_listener
+ */
+struct zxdg_output_v1_listener {
+ /**
+ * position of the output within the global compositor space
+ *
+ * The position event describes the location of the wl_output
+ * within the global compositor space.
+ *
+ * The logical_position event is sent after creating an xdg_output
+ * (see xdg_output_manager.get_xdg_output) and whenever the
+ * location of the output changes within the global compositor
+ * space.
+ * @param x x position within the global compositor space
+ * @param y y position within the global compositor space
+ */
+ void (*logical_position)(void* data,
+ struct zxdg_output_v1* zxdg_output_v1,
+ int32_t x,
+ int32_t y);
+ /**
+ * size of the output in the global compositor space
+ *
+ * The logical_size event describes the size of the output in the
+ * global compositor space.
+ *
+ * Most regular Wayland clients should not pay attention to the
+ * logical size and would rather rely on xdg_shell interfaces.
+ *
+ * Some clients such as Xwayland, however, need this to configure
+ * their surfaces in the global compositor space as the compositor
+ * may apply a different scale from what is advertised by the
+ * output scaling property (to achieve fractional scaling, for
+ * example).
+ *
+ * For example, for a wl_output mode 3840×2160 and a scale factor
+ * 2:
+ *
+ * - A compositor not scaling the monitor viewport in its
+ * compositing space will advertise a logical size of 3840×2160,
+ *
+ * - A compositor scaling the monitor viewport with scale factor 2
+ * will advertise a logical size of 1920×1080,
+ *
+ * - A compositor scaling the monitor viewport using a fractional
+ * scale of 1.5 will advertise a logical size of 2560×1440.
+ *
+ * For example, for a wl_output mode 1920×1080 and a 90 degree
+ * rotation, the compositor will advertise a logical size of
+ * 1080x1920.
+ *
+ * The logical_size event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output) and whenever the logical size
+ * of the output changes, either as a result of a change in the
+ * applied scale or because of a change in the corresponding output
+ * mode(see wl_output.mode) or transform (see wl_output.transform).
+ * @param width width in global compositor space
+ * @param height height in global compositor space
+ */
+ void (*logical_size)(void* data,
+ struct zxdg_output_v1* zxdg_output_v1,
+ int32_t width,
+ int32_t height);
+ /**
+ * all information about the output have been sent
+ *
+ * This event is sent after all other properties of an xdg_output
+ * have been sent.
+ *
+ * This allows changes to the xdg_output properties to be seen as
+ * atomic, even if they happen via multiple events.
+ *
+ * For objects version 3 onwards, this event is deprecated.
+ * Compositors are not required to send it anymore and must send
+ * wl_output.done instead.
+ */
+ void (*done)(void* data,
+ struct zxdg_output_v1* zxdg_output_v1);
+ /**
+ * name of this output
+ *
+ * Many compositors will assign names to their outputs, show them
+ * to the user, allow them to be configured by name, etc. The
+ * client may wish to know this name as well to offer the user
+ * similar behaviors.
+ *
+ * The naming convention is compositor defined, but limited to
+ * alphanumeric characters and dashes (-). Each name is unique
+ * among all wl_output globals, but if a wl_output global is
+ * destroyed the same name may be reused later. The names will also
+ * remain consistent across sessions with the same hardware and
+ * software configuration.
+ *
+ * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc.
+ * However, do not assume that the name is a reflection of an
+ * underlying DRM connector, X11 connection, etc.
+ *
+ * The name event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output). This event is only sent once
+ * per xdg_output, and the name does not change over the lifetime
+ * of the wl_output global.
+ *
+ * This event is deprecated, instead clients should use
+ * wl_output.name. Compositors must still support this event.
+ * @param name output name
+ * @since 2
+ */
+ void (*name)(void* data,
+ struct zxdg_output_v1* zxdg_output_v1,
+ const char* name);
+ /**
+ * human-readable description of this output
+ *
+ * Many compositors can produce human-readable descriptions of
+ * their outputs. The client may wish to know this description as
+ * well, to communicate the user for various purposes.
+ *
+ * The description is a UTF-8 string with no convention defined for
+ * its contents. Examples might include 'Foocorp 11" Display' or
+ * 'Virtual X11 output via :1'.
+ *
+ * The description event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output) and whenever the description
+ * changes. The description is optional, and may not be sent at
+ * all.
+ *
+ * For objects of version 2 and lower, this event is only sent once
+ * per xdg_output, and the description does not change over the
+ * lifetime of the wl_output global.
+ *
+ * This event is deprecated, instead clients should use
+ * wl_output.description. Compositors must still support this
+ * event.
+ * @param description output description
+ * @since 2
+ */
+ void (*description)(void* data,
+ struct zxdg_output_v1* zxdg_output_v1,
+ const char* description);
+};
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+static inline int
+zxdg_output_v1_add_listener(struct zxdg_output_v1* zxdg_output_v1,
+ const struct zxdg_output_v1_listener* listener,
+ void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*) zxdg_output_v1,
+ (void (**)(void)) listener,
+ data);
+}
+
+#define ZXDG_OUTPUT_V1_DESTROY 0
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1
+
+// /** @ingroup iface_zxdg_output_v1 */
+// static inline void
+// zxdg_output_v1_set_user_data(struct zxdg_output_v1 *zxdg_output_v1, void *user_data)
+// {
+// wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_v1, user_data);
+// }
+
+// /** @ingroup iface_zxdg_output_v1 */
+// static inline void *
+// zxdg_output_v1_get_user_data(struct zxdg_output_v1 *zxdg_output_v1)
+// {
+// return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_v1);
+// }
+
+// static inline uint32_t
+// zxdg_output_v1_get_version(struct zxdg_output_v1 *zxdg_output_v1)
+// {
+// return wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1);
+// }
+
+// /**
+// * @ingroup iface_zxdg_output_v1
+// *
+// * Using this request a client can tell the server that it is not
+// * going to use the xdg_output object anymore.
+// */
+// static inline void
+// zxdg_output_v1_destroy(struct zxdg_output_v1 *zxdg_output_v1)
+// {
+// wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_v1,
+// ZXDG_OUTPUT_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1), WL_MARSHAL_FLAG_DESTROY);
+// }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c
new file mode 100644
index 0000000..76f6257
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c
@@ -0,0 +1,76 @@
+#ifdef FF_HAVE_WAYLAND
+/* Generated by wayland-scanner 1.22.0 */
+
+/*
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+ #include <stdlib.h>
+ #include <stdint.h>
+ #include <wayland-util.h>
+
+extern const struct wl_interface wl_output_interface;
+extern const struct wl_interface zxdg_output_v1_interface;
+
+static const struct wl_interface* xdg_output_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ &zxdg_output_v1_interface,
+ NULL, // &wl_output_interface,
+};
+
+static const struct wl_message zxdg_output_manager_v1_requests[] = {
+ { "destroy", "", xdg_output_unstable_v1_types + 0 },
+ { "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 },
+};
+
+WL_EXPORT const struct wl_interface zxdg_output_manager_v1_interface = {
+ "zxdg_output_manager_v1",
+ 3,
+ 2,
+ zxdg_output_manager_v1_requests,
+ 0,
+ NULL,
+};
+
+static const struct wl_message zxdg_output_v1_requests[] = {
+ { "destroy", "", xdg_output_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zxdg_output_v1_events[] = {
+ { "logical_position", "ii", xdg_output_unstable_v1_types + 0 },
+ { "logical_size", "ii", xdg_output_unstable_v1_types + 0 },
+ { "done", "", xdg_output_unstable_v1_types + 0 },
+ { "name", "2s", xdg_output_unstable_v1_types + 0 },
+ { "description", "2s", xdg_output_unstable_v1_types + 0 },
+};
+
+WL_EXPORT const struct wl_interface zxdg_output_v1_interface = {
+ "zxdg_output_v1",
+ 3,
+ 1,
+ zxdg_output_v1_requests,
+ 5,
+ zxdg_output_v1_events,
+};
+
+#endif
diff --git a/src/detection/displayserver/linux/wayland/zwlr-output.c b/src/detection/displayserver/linux/wayland/zwlr-output.c
new file mode 100644
index 0000000..632bd19
--- /dev/null
+++ b/src/detection/displayserver/linux/wayland/zwlr-output.c
@@ -0,0 +1,212 @@
+#ifdef FF_HAVE_WAYLAND
+
+ #include "wayland.h"
+ #include "wlr-output-management-unstable-v1-client-protocol.h"
+
+static void waylandZwlrTransformListener(void* data, FF_A_UNUSED struct zwlr_output_head_v1* zwlr_output_head_v1, int32_t transform) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ wldata->transform = (enum wl_output_transform) transform;
+}
+
+static void waylandZwlrScaleListener(void* data, FF_A_UNUSED struct zwlr_output_head_v1* zwlr_output_head_v1, wl_fixed_t scale) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96;
+}
+
+typedef struct WaylandZwlrMode {
+ int32_t width;
+ int32_t height;
+ int32_t refreshRate;
+ bool preferred;
+ struct zwlr_output_mode_v1* pMode;
+} WaylandZwlrMode;
+
+static void waylandZwlrModeSizeListener(void* data, FF_A_UNUSED struct zwlr_output_mode_v1* zwlr_output_mode_v1, int32_t width, int32_t height) {
+ WaylandZwlrMode* mode = (WaylandZwlrMode*) data;
+ mode->width = width;
+ mode->height = height;
+}
+
+static void waylandZwlrModeRefreshListener(void* data, FF_A_UNUSED struct zwlr_output_mode_v1* zwlr_output_mode_v1, int32_t rate) {
+ WaylandZwlrMode* mode = (WaylandZwlrMode*) data;
+ mode->refreshRate = rate;
+}
+
+static void waylandZwlrModePreferredListener(void* data, FF_A_UNUSED struct zwlr_output_mode_v1* zwlr_output_mode_v1) {
+ WaylandZwlrMode* mode = (WaylandZwlrMode*) data;
+ mode->preferred = true;
+}
+
+static const struct zwlr_output_mode_v1_listener modeListener = {
+ .size = waylandZwlrModeSizeListener,
+ .refresh = waylandZwlrModeRefreshListener,
+ .preferred = waylandZwlrModePreferredListener,
+ .finished = (void*) stubListener,
+};
+
+static void waylandZwlrModeListener(void* data, FF_A_UNUSED struct zwlr_output_head_v1* zwlr_output_head_v1, struct zwlr_output_mode_v1* mode) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ if (!wldata->internal) {
+ return;
+ }
+
+ WaylandZwlrMode* newMode = FF_LIST_ADD(WaylandZwlrMode, *(FFlist*) wldata->internal);
+ *newMode = (WaylandZwlrMode) { .pMode = mode };
+
+ // Strangely, the listener is called only in this function, but not in `waylandZwlrCurrentModeListener`
+ wldata->parent->ffwl_proxy_add_listener((struct wl_proxy*) mode, (void (**)(void)) &modeListener, newMode);
+}
+
+static void waylandZwlrCurrentModeListener(void* data, FF_A_UNUSED struct zwlr_output_head_v1* zwlr_output_head_v1, struct zwlr_output_mode_v1* mode) {
+ // waylandZwlrModeListener is always run before this
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ if (!wldata->internal) {
+ return;
+ }
+
+ int set = 0;
+ FF_LIST_FOR_EACH (WaylandZwlrMode, m, *(FFlist*) wldata->internal) {
+ if (m->pMode == mode) {
+ wldata->width = m->width;
+ wldata->height = m->height;
+ wldata->refreshRate = m->refreshRate;
+ if (++set == 2) {
+ break;
+ }
+ }
+ if (m->preferred) {
+ wldata->preferredWidth = m->width;
+ wldata->preferredHeight = m->height;
+ wldata->preferredRefreshRate = m->refreshRate;
+ if (++set == 2) {
+ break;
+ }
+ }
+ }
+}
+
+static void waylandZwlrPhysicalSizeListener(void* data, FF_A_UNUSED struct zwlr_output_head_v1* zwlr_output_head_v1, int32_t width, int32_t height) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ wldata->physicalWidth = width;
+ wldata->physicalHeight = height;
+}
+
+static void waylandZwlrEnabledListener(void* data, FF_A_UNUSED struct zwlr_output_head_v1* zwlr_output_head_v1, bool enabled) {
+ WaylandDisplay* wldata = (WaylandDisplay*) data;
+ if (!enabled) {
+ wldata->internal = NULL;
+ }
+}
+
+static const struct zwlr_output_head_v1_listener headListener = {
+ .name = (void*) ffWaylandOutputNameListener,
+ .description = (void*) ffWaylandOutputDescriptionListener,
+ .physical_size = waylandZwlrPhysicalSizeListener,
+ .mode = waylandZwlrModeListener,
+ .enabled = (void*) waylandZwlrEnabledListener,
+ .current_mode = waylandZwlrCurrentModeListener,
+ .position = (void*) stubListener,
+ .transform = waylandZwlrTransformListener,
+ .scale = waylandZwlrScaleListener,
+ .finished = (void*) stubListener,
+ .make = (void*) stubListener,
+ .model = (void*) stubListener,
+ .serial_number = (void*) stubListener,
+ .adaptive_sync = (void*) stubListener,
+};
+
+static void waylandHandleZwlrHead(void* data, FF_A_UNUSED struct zwlr_output_manager_v1* zwlr_output_manager_v1, struct zwlr_output_head_v1* head) {
+ WaylandData* wldata = data;
+
+ FF_LIST_AUTO_DESTROY modes = ffListCreate();
+ WaylandDisplay display = {
+ .parent = wldata,
+ .transform = WL_OUTPUT_TRANSFORM_NORMAL,
+ .type = FF_DISPLAY_TYPE_UNKNOWN,
+ .name = ffStrbufCreate(),
+ .description = ffStrbufCreate(),
+ .edidName = ffStrbufCreate(),
+ .internal = &modes,
+ };
+
+ wldata->ffwl_proxy_add_listener((struct wl_proxy*) head, (void (**)(void)) &headListener, &display);
+ wldata->ffwl_display_roundtrip(wldata->display);
+
+ if (display.width <= 0 || display.height <= 0 || !display.internal) {
+ return;
+ }
+
+ uint32_t rotation = ffWaylandHandleRotation(&display);
+
+ FFDisplayResult* item = ffdsAppendDisplay(wldata->result,
+ (uint32_t) display.width,
+ (uint32_t) display.height,
+ display.refreshRate / 1000.0,
+ (uint32_t) display.dpi,
+ (uint32_t) display.preferredWidth,
+ (uint32_t) display.preferredHeight,
+ display.preferredRefreshRate / 1000.0,
+ rotation,
+ display.edidName.length
+ ? &display.edidName
+ : display.description.length && !ffStrbufContain(&display.description, &display.name)
+ ? &display.description
+ : &display.name,
+ display.type,
+ false,
+ display.id,
+ (uint32_t) display.physicalWidth,
+ (uint32_t) display.physicalHeight,
+ "wayland-zwlr");
+ if (item) {
+ if (display.hdrSupported) {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
+ } else if (display.hdrInfoAvailable) {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ } else {
+ item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
+ }
+
+ item->manufactureYear = display.myear;
+ item->manufactureWeek = display.mweek;
+ item->serial = display.serial;
+ }
+
+ ffStrbufDestroy(&display.description);
+ ffStrbufDestroy(&display.name);
+ ffStrbufDestroy(&display.edidName);
+
+ // These must be released manually
+ FF_LIST_FOR_EACH (WaylandZwlrMode, m, modes) {
+ wldata->ffwl_proxy_destroy((void*) m->pMode);
+ }
+ wldata->ffwl_proxy_destroy((void*) head);
+}
+
+static const struct zwlr_output_manager_v1_listener outputListener = {
+ .head = waylandHandleZwlrHead,
+ .done = (void*) stubListener,
+ .finished = (void*) stubListener,
+};
+
+const char* ffWaylandHandleZwlrOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
+ uint32_t bindVersion = min(version, ZWLR_OUTPUT_MANAGER_V1_HEAD_SINCE_VERSION);
+ struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zwlr_output_manager_v1_interface, bindVersion, name, zwlr_output_manager_v1_interface.name, bindVersion, NULL);
+ if (output == NULL) {
+ return "Failed to bind zwlr_output_manager_v1";
+ }
+
+ if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, wldata) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to add listener to zwlr_output_manager_v1";
+ }
+ if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
+ wldata->ffwl_proxy_destroy(output);
+ return "Failed to roundtrip display";
+ }
+ wldata->ffwl_proxy_destroy(output);
+
+ return NULL;
+}
+
+#endif
diff --git a/src/detection/displayserver/linux/wmde.c b/src/detection/displayserver/linux/wmde.c
new file mode 100644
index 0000000..f0321c8
--- /dev/null
+++ b/src/detection/displayserver/linux/wmde.c
@@ -0,0 +1,523 @@
+#include "displayserver_linux.h"
+#include "common/io.h"
+#include "common/properties.h"
+#include "common/strutil.h"
+#include "common/mallocHelper.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if __FreeBSD__
+ #include <sys/sysctl.h>
+ #include <sys/types.h>
+ #include <sys/user.h>
+#elif __OpenBSD__
+ #include <sys/param.h>
+ #include <sys/sysctl.h>
+ #include <kvm.h>
+#elif __sun
+ #include <procfs.h>
+#elif __NetBSD__
+ #include <sys/types.h>
+ #include <sys/sysctl.h>
+#endif
+
+static const char* parseEnv(void) {
+ const char* env;
+
+ env = getenv("XDG_CURRENT_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("XDG_SESSION_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("CURRENT_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("SESSION_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("DESKTOP_SESSION");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ if (getenv("KDE_FULL_SESSION") != NULL || getenv("KDE_SESSION_UID") != NULL || getenv("KDE_SESSION_VERSION") != NULL) {
+ return "KDE";
+ }
+
+ if (getenv("GNOME_DESKTOP_SESSION_ID") != NULL) {
+ return "GNOME";
+ }
+
+ if (getenv("MATE_DESKTOP_SESSION_ID") != NULL) {
+ return "Mate";
+ }
+
+ if (getenv("TDE_FULL_SESSION") != NULL) {
+ return "Trinity";
+ }
+
+ if (getenv("HYPRLAND_CMD") != NULL) {
+ return "Hyprland";
+ }
+
+ if (getenv("SWAYSOCK") != NULL) {
+ return "Sway";
+ }
+
+#if __linux__ && !__ANDROID__
+ if (
+ getenv("WAYLAND_DISPLAY") != NULL &&
+ ffPathExists("/mnt/wslg/", FF_PATHTYPE_DIRECTORY)) {
+ return "WSLg";
+ }
+#endif
+
+ return NULL;
+}
+
+static void applyPrettyNameIfWM(FFDisplayServerResult* result, const char* name) {
+ if (!ffStrSet(name)) {
+ return;
+ }
+
+ if (
+ ffStrEqualsIgnCase(name, "kwin") ||
+ ffStrStartsWithIgnCase(name, "kwin_") ||
+ ffStrEndsWithIgnCase(name, "-kwin_wayland") ||
+ ffStrEndsWithIgnCase(name, "-kwin_x11")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_KWIN);
+ } else if (
+ ffStrEqualsIgnCase(name, "gnome-shell") ||
+ ffStrEqualsIgnCase(name, "gnome shell") ||
+ ffStrEqualsIgnCase(name, "gnome-session-binary") ||
+ ffStrEqualsIgnCase(name, "Mutter")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUTTER);
+ } else if (
+ ffStrEqualsIgnCase(name, "cinnamon") ||
+ ffStrStartsWithIgnCase(name, "cinnamon-") ||
+ ffStrEqualsIgnCase(name, "Muffin") ||
+ ffStrEqualsIgnCase(name, "Mutter (Muffin)")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUFFIN);
+ } else if (ffStrEqualsIgnCase(name, "sway")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_SWAY);
+ } else if (ffStrEqualsIgnCase(name, "weston")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WESTON);
+ } else if (ffStrEqualsIgnCase(name, "wayfire")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WAYFIRE);
+ } else if (ffStrEqualsIgnCase(name, "openbox")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_OPENBOX);
+ } else if (ffStrEqualsIgnCase(name, "xfwm4")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XFWM4);
+ } else if (ffStrEqualsIgnCase(name, "Marco") ||
+ ffStrEqualsIgnCase(name, "Metacity (Marco)")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MARCO);
+ } else if (ffStrEqualsIgnCase(name, "xmonad")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XMONAD);
+ } else if (ffStrEqualsIgnCase(name, "WSLg")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WSLG);
+ } else if (ffStrEqualsIgnCase(name, "dwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DWM);
+ } else if (ffStrEqualsIgnCase(name, "bspwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_BSPWM);
+ } else if (ffStrEqualsIgnCase(name, "tinywm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_TINYWM);
+ } else if (ffStrEqualsIgnCase(name, "qtile")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_QTILE);
+ } else if (ffStrEqualsIgnCase(name, "herbstluftwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HERBSTLUFTWM);
+ } else if (ffStrEqualsIgnCase(name, "icewm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_ICEWM);
+ } else if (ffStrEqualsIgnCase(name, "dtwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DTWM);
+ } else if (ffStrEqualsIgnCase(name, "fvwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_FVWM);
+ } else if (ffStrEqualsIgnCase(name, "ctwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_CTWM);
+ } else if (ffStrEqualsIgnCase(name, "hyprland")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HYPRLAND);
+ } else if (ffStrEqualsIgnCase(name, "ratpoison")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_RATPOISON);
+ }
+}
+
+static void applyNameIfWM(FFDisplayServerResult* result, const char* processName) {
+ applyPrettyNameIfWM(result, processName);
+ if (result->wmPrettyName.length > 0) {
+ ffStrbufSetS(&result->wmProcessName, processName);
+ }
+}
+
+static void applyBetterWM(FFDisplayServerResult* result, const char* processName) {
+ if (!ffStrSet(processName)) {
+ return;
+ }
+
+ ffStrbufSetS(&result->wmProcessName, processName);
+
+ // If it is a known wm, this will set the pretty name
+ applyPrettyNameIfWM(result, processName);
+
+ // If it isn't a known wm, set the pretty name to the process name
+ if (result->wmPrettyName.length == 0) {
+ ffStrbufAppend(&result->wmPrettyName, &result->wmProcessName);
+ }
+}
+
+static void applyPrettyNameIfDE(FFDisplayServerResult* result, const char* name) {
+ if (!ffStrSet(name)) {
+ return;
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "KDE") ||
+ ffStrEqualsIgnCase(name, "plasma") ||
+ ffStrEqualsIgnCase(name, "plasmashell") ||
+ ffStrEqualsIgnCase(name, "plasmawayland")) {
+ ffStrbufSetStatic(&result->deProcessName, "plasmashell");
+ ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_PLASMA);
+ applyBetterWM(result, getenv("KDEWM"));
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "GNOME") ||
+ ffStrEqualsIgnCase(name, "ubuntu:GNOME") ||
+ ffStrEqualsIgnCase(name, "ubuntu") ||
+ ffStrEqualsIgnCase(name, "gnome-shell")) {
+ ffStrbufSetStatic(&result->deProcessName, "gnome-shell");
+ const char* sessionMode = getenv("GNOME_SHELL_SESSION_MODE");
+ if (sessionMode && ffStrEquals(sessionMode, "classic")) {
+ ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC);
+ } else {
+ ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME);
+ }
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "X-Cinnamon") ||
+ ffStrEqualsIgnCase(name, "Cinnamon")) {
+ ffStrbufSetS(&result->deProcessName, "cinnamon");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CINNAMON);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "XFCE") ||
+ ffStrEqualsIgnCase(name, "X-XFCE") ||
+ ffStrEqualsIgnCase(name, "XFCE4") ||
+ ffStrEqualsIgnCase(name, "X-XFCE4") ||
+ ffStrEqualsIgnCase(name, "xfce4-session")) {
+ ffStrbufSetS(&result->deProcessName, "xfce4-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_XFCE4);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "MATE") ||
+ ffStrEqualsIgnCase(name, "X-MATE") ||
+ ffStrEqualsIgnCase(name, "mate-session")) {
+ ffStrbufSetS(&result->deProcessName, "mate-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_MATE);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "LXQt") ||
+ ffStrEqualsIgnCase(name, "X-LXQt") ||
+ ffStrEqualsIgnCase(name, "lxqt-session")) {
+ ffStrbufSetS(&result->deProcessName, "lxqt-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_LXQT);
+ if (result->wmProcessName.length == 0) {
+ FF_STRBUF_AUTO_DESTROY wmProcessNameBuffer = ffStrbufCreate();
+ ffParsePropFileConfig("lxqt/session.conf", "window_manager =", &wmProcessNameBuffer);
+ applyBetterWM(result, wmProcessNameBuffer.chars);
+ }
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "Budgie") ||
+ ffStrEqualsIgnCase(name, "X-Budgie") ||
+ ffStrEqualsIgnCase(name, "budgie-desktop") ||
+ ffStrEqualsIgnCase(name, "Budgie:GNOME")) {
+ ffStrbufSetS(&result->deProcessName, "budgie-desktop");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_BUDGIE);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "dtsession")) {
+ ffStrbufSetS(&result->deProcessName, "dtsession");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CDE);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "ukui-session")) {
+ ffStrbufSetS(&result->deProcessName, "ukui-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UKUI);
+ }
+
+ else if (
+ ffStrStartsWithIgnCase(name, "Unity:Unity")) {
+ ffStrbufSetS(&result->deProcessName, "unity-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UNITY);
+ }
+
+ else if (ffStrEqualsIgnCase(name, "Enlightenment")) {
+ ffStrbufSetS(&result->deProcessName, "enlightenment_start");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT);
+ }
+}
+
+static const char* getFromProcesses(FFDisplayServerResult* result) {
+ uint32_t userId = instance.state.platform.uid;
+
+#if __FreeBSD__
+ #ifdef __DragonFly__
+ #define ki_comm kp_comm
+ #endif
+
+ int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId };
+ size_t length = 0;
+
+ if (sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0) {
+ return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, NULL) failed";
+ }
+
+ FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
+ if (sysctl(request, ARRAY_SIZE(request), procs, &length, NULL, 0) != 0) {
+ return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
+ }
+
+ length /= sizeof(*procs);
+
+ for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc->ki_comm);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc->ki_comm);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+#elif __OpenBSD__
+ kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL);
+ int count = 0;
+ const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_UID, (int) userId, sizeof(*proc), &count);
+ if (proc) {
+ for (int i = 0; i < count; ++i) {
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc[i].p_comm);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc[i].p_comm);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+ }
+ kvm_close(kd);
+#elif __sun
+ FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
+ if (procdir == NULL) {
+ return "opendir(\"/proc\") failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
+ ffStrbufAppendS(&procPath, "/proc/");
+
+ uint32_t procPathLength = procPath.length;
+
+ struct dirent* dirent;
+ while ((dirent = readdir(procdir)) != NULL) {
+ if (!ffCharIsDigit(dirent->d_name[0])) {
+ continue;
+ }
+
+ ffStrbufAppendS(&procPath, dirent->d_name);
+ ffStrbufAppendS(&procPath, "/psinfo");
+ psinfo_t proc;
+ if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
+ ffStrbufSubstrBefore(&procPath, procPathLength);
+
+ if (proc.pr_uid != userId) {
+ continue;
+ }
+
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc.pr_fname);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc.pr_fname);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+ }
+#elif __linux__ || __GNU__
+ FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
+ if (procdir == NULL) {
+ return "opendir(\"/proc\") failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
+ ffStrbufAppendS(&procPath, "/proc/");
+
+ uint32_t procPathLength = procPath.length;
+
+ FF_STRBUF_AUTO_DESTROY loginuid = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY processName = ffStrbufCreateA(256); // Some processes have large command lines (looking at you chrome)
+
+ struct dirent* dirent;
+ while ((dirent = readdir(procdir)) != NULL) {
+ // Match only folders starting with a number (the pid folders)
+ if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
+ continue;
+ }
+
+ ffStrbufAppendS(&procPath, dirent->d_name);
+ uint32_t procFolderPathLength = procPath.length;
+
+ // Don't check for processes not owned by the current user.
+ ffStrbufAppendS(&procPath, "/loginuid");
+ ffReadFileBuffer(procPath.chars, &loginuid);
+ if (ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId) {
+ ffStrbufSubstrBefore(&procPath, procPathLength);
+ continue;
+ }
+
+ ffStrbufSubstrBefore(&procPath, procFolderPathLength);
+
+ // We check the cmdline for the process name, because it is not trimmed.
+ ffStrbufAppendS(&procPath, "/cmdline");
+ ffReadFileBuffer(procPath.chars, &processName);
+ ffStrbufTrimRightSpace(&processName);
+ ffStrbufSubstrBeforeFirstC(&processName, '\0'); // Trim the arguments
+ ffStrbufSubstrAfterLastC(&processName, '/');
+
+ ffStrbufSubstrBefore(&procPath, procPathLength);
+
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, processName.chars);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, processName.chars);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+#elif __NetBSD__
+ int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, (int) userId, sizeof(struct kinfo_proc2), INT_MAX };
+
+ size_t size = 0;
+ if (sysctl(request, ARRAY_SIZE(request), NULL, &size, NULL, 0) != 0) {
+ return "sysctl(KERN_PROC_UID, NULL) failed";
+ }
+
+ FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
+
+ if (sysctl(request, ARRAY_SIZE(request), procs, &size, NULL, 0) != 0) {
+ return "sysctl(KERN_PROC_UID, procs) failed";
+ }
+
+ for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc->p_comm);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc->p_comm);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+#endif
+
+ return NULL;
+}
+
+void ffdsDetectWMDE(FFDisplayServerResult* result) {
+#if __ANDROID__
+ if (ffStrbufIgnCaseEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
+ return; // Only supported when connected to X11
+ }
+#endif
+
+ const char* env = parseEnv();
+
+ if (result->wmProcessName.length > 0) {
+ // If we found the processName via display server, use it.
+ // This will set the pretty name if it is a known WM, otherwise the prettyName to the processName
+ applyPrettyNameIfWM(result, result->wmProcessName.chars);
+ if (result->wmPrettyName.length == 0) {
+ ffStrbufSet(&result->wmPrettyName, &result->wmProcessName);
+ }
+ } else {
+ // if env is a known WM, use it
+ applyNameIfWM(result, env);
+ }
+
+ // Connecting to a display server only gives WM results, not DE results.
+ // If we find it in the environment, use that.
+ applyPrettyNameIfDE(result, env);
+
+ // If WM was found by connection to the sever, and DE in the environment, we can return
+ // This way we never call getFromProcDir(), which has slow initialization time
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ return;
+ }
+
+ // Get missing WM / DE from processes.
+ getFromProcesses(result);
+
+ // Return if both wm and de are set, or if env doesn't contain anything
+ if (
+ (result->wmPrettyName.length > 0 && result->dePrettyName.length > 0) ||
+ !ffStrSet(env)) {
+ return;
+ }
+
+ // If nothing is set, use env as WM
+ else if (result->wmPrettyName.length == 0 && result->dePrettyName.length == 0) {
+ ffStrbufSetS(&result->wmProcessName, env);
+ ffStrbufSetS(&result->wmPrettyName, env);
+ }
+
+ // If only WM is not set, and DE doesn't equal env, use env as WM
+ else if (
+ result->wmPrettyName.length == 0 &&
+ ffStrbufIgnCaseCompS(&result->deProcessName, env) != 0 &&
+ ffStrbufIgnCaseCompS(&result->dePrettyName, env) != 0) {
+ ffStrbufSetS(&result->wmProcessName, env);
+ ffStrbufSetS(&result->wmPrettyName, env);
+ }
+
+ // If only DE is not set, and WM doesn't equal env, use env as DE
+ else if (
+ result->dePrettyName.length == 0 &&
+ ffStrbufIgnCaseCompS(&result->wmProcessName, env) != 0 &&
+ ffStrbufIgnCaseCompS(&result->wmPrettyName, env) != 0) {
+ ffStrbufSetS(&result->deProcessName, env);
+ ffStrbufSetS(&result->dePrettyName, env);
+ }
+}
diff --git a/src/detection/displayserver/linux/xcb.c b/src/detection/displayserver/linux/xcb.c
new file mode 100644
index 0000000..8620d71
--- /dev/null
+++ b/src/detection/displayserver/linux/xcb.c
@@ -0,0 +1,441 @@
+#include "displayserver_linux.h"
+
+#ifdef FF_HAVE_XCB_RANDR
+
+ #include "common/library.h"
+ #include "common/properties.h"
+ #include "common/edidHelper.h"
+ #include "common/mallocHelper.h"
+ #include "common/strutil.h"
+
+ #include <stdlib.h>
+ #include <string.h>
+ #include <xcb/randr.h>
+ #include <xcb/xcb.h>
+
+typedef struct XcbRandrData {
+ FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_reply)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_modes_iterator)
+ FF_LIBRARY_SYMBOL(xcb_randr_mode_info_next)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_monitors)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_reply)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_monitors_iterator)
+ FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_next)
+ FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs_length)
+ FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs)
+ FF_LIBRARY_SYMBOL(xcb_randr_output_next)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_output_info)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_output_info_reply)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info_reply)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_output_property)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_reply)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data)
+ FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data_length)
+
+ FF_LIBRARY_SYMBOL(xcb_intern_atom)
+ FF_LIBRARY_SYMBOL(xcb_intern_atom_reply)
+ FF_LIBRARY_SYMBOL(xcb_get_property)
+ FF_LIBRARY_SYMBOL(xcb_get_property_reply)
+ FF_LIBRARY_SYMBOL(xcb_get_property_value)
+ FF_LIBRARY_SYMBOL(xcb_get_property_value_length)
+ FF_LIBRARY_SYMBOL(xcb_get_atom_name)
+ FF_LIBRARY_SYMBOL(xcb_get_atom_name_name)
+ FF_LIBRARY_SYMBOL(xcb_get_atom_name_name_length)
+ FF_LIBRARY_SYMBOL(xcb_get_atom_name_reply)
+ FF_LIBRARY_SYMBOL(xcb_get_setup)
+ FF_LIBRARY_SYMBOL(xcb_setup_vendor)
+ FF_LIBRARY_SYMBOL(xcb_setup_vendor_length)
+
+ // init once
+ xcb_connection_t* connection;
+ FFDisplayServerResult* result;
+} XcbRandrData;
+
+static void* xcbGetProperty(XcbRandrData* data, xcb_window_t window, const char* request) {
+ xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(request), request);
+ FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL);
+ if (requestAtomReply == NULL) {
+ return NULL;
+ }
+
+ xcb_get_property_cookie_t propertyCookie = data->ffxcb_get_property(data->connection, false, window, requestAtomReply->atom, XCB_ATOM_ANY, 0, 8 * 1024);
+ FF_AUTO_FREE xcb_get_property_reply_t* propertyReply = data->ffxcb_get_property_reply(data->connection, propertyCookie, NULL);
+ if (propertyReply == NULL) {
+ return NULL;
+ }
+
+ int length = data->ffxcb_get_property_value_length(propertyReply);
+ if (length <= 0) {
+ return NULL;
+ }
+
+ // Why are xcb property strings not null terminated???
+ void* replyValue = malloc((size_t) length + 1);
+ memcpy(replyValue, data->ffxcb_get_property_value(propertyReply), (size_t) length);
+ ((char*) replyValue)[length] = '\0';
+
+ return replyValue;
+}
+
+static xcb_randr_get_output_property_reply_t* xcbRandrGetProperty(XcbRandrData* data, xcb_randr_output_t output, const char* name) {
+ xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(name), name);
+ FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL);
+
+ if (requestAtomReply) {
+ xcb_randr_get_output_property_cookie_t outputPropertyCookie = data->ffxcb_randr_get_output_property(data->connection, output, requestAtomReply->atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 100, false, false);
+ return data->ffxcb_randr_get_output_property_reply(data->connection, outputPropertyCookie, NULL);
+ }
+ return NULL;
+}
+
+static void xcbDetectWMfromEWMH(XcbRandrData* data, xcb_window_t rootWindow, FFDisplayServerResult* result) {
+ if (result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND)) {
+ return;
+ }
+
+ FF_AUTO_FREE xcb_window_t* wmWindow = (xcb_window_t*) xcbGetProperty(data, rootWindow, "_NET_SUPPORTING_WM_CHECK");
+ if (wmWindow == NULL) {
+ return;
+ }
+
+ FF_AUTO_FREE char* wmName = (char*) xcbGetProperty(data, *wmWindow, "WM_NAME");
+ if (!ffStrSet(wmName)) {
+ wmName = (char*) xcbGetProperty(data, *wmWindow, "_NET_WM_NAME");
+ }
+
+ if (!ffStrSet(wmName)) {
+ return;
+ }
+
+ ffStrbufSetS(&result->wmProcessName, wmName);
+}
+
+static void xcbFetchServerVendor(XcbRandrData* data, FFDisplayServerResult* result) {
+ const xcb_setup_t* setup = data->ffxcb_get_setup(data->connection);
+
+ int length = data->ffxcb_setup_vendor_length(setup);
+ if (length <= 0) {
+ return;
+ }
+
+ FF_STRBUF_AUTO_DESTROY serverVendor = ffStrbufCreateNS((uint32_t) length, data->ffxcb_setup_vendor(setup));
+
+ if (!ffStrbufEqualS(&serverVendor, "The X.Org Foundation")) // Original
+ {
+ ffStrbufDestroy(&result->wmProtocolName);
+ ffStrbufInitMove(&result->wmProtocolName, &serverVendor);
+ }
+}
+
+static bool xcbRandrHandleOutput(XcbRandrData* data, xcb_randr_output_t output, FFstrbuf* name, bool primary, FFDisplayType displayType, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi) {
+ xcb_randr_get_output_info_cookie_t outputInfoCookie = data->ffxcb_randr_get_output_info(data->connection, output, XCB_CURRENT_TIME);
+ FF_AUTO_FREE xcb_randr_get_output_info_reply_t* outputInfoReply = data->ffxcb_randr_get_output_info_reply(data->connection, outputInfoCookie, NULL);
+ if (outputInfoReply == NULL) {
+ return false;
+ }
+
+ FF_AUTO_FREE xcb_randr_get_output_property_reply_t* edidReply = xcbRandrGetProperty(data, output, "EDID");
+ uint8_t* edidData = NULL;
+ uint32_t edidLength = 0;
+ if (edidReply) {
+ int len = data->ffxcb_randr_get_output_property_data_length(edidReply);
+ if (len >= 128) {
+ edidData = data->ffxcb_randr_get_output_property_data(edidReply);
+ edidLength = (uint32_t) len;
+ }
+ }
+
+ if (edidData) {
+ ffStrbufClear(name);
+ ffEdidGetName(edidData, name);
+ }
+
+ bool randrEmulation = false;
+ FF_AUTO_FREE xcb_randr_get_output_property_reply_t* randrEmulationReply = xcbRandrGetProperty(data, output, "RANDR Emulation");
+ if (randrEmulationReply) {
+ int len = data->ffxcb_randr_get_output_property_data_length(randrEmulationReply);
+ if (len >= 1) {
+ randrEmulation = !!data->ffxcb_randr_get_output_property_data(randrEmulationReply)[0];
+ }
+ }
+
+ xcb_randr_get_crtc_info_cookie_t crtcInfoCookie = data->ffxcb_randr_get_crtc_info(data->connection, outputInfoReply->crtc, XCB_CURRENT_TIME);
+ FF_AUTO_FREE xcb_randr_get_crtc_info_reply_t* crtcInfoReply = data->ffxcb_randr_get_crtc_info_reply(data->connection, crtcInfoCookie, NULL);
+ if (crtcInfoReply == NULL) {
+ return false;
+ }
+
+ uint32_t rotation;
+ switch (crtcInfoReply->rotation) {
+ case XCB_RANDR_ROTATION_ROTATE_90:
+ rotation = 90;
+ break;
+ case XCB_RANDR_ROTATION_ROTATE_180:
+ rotation = 180;
+ break;
+ case XCB_RANDR_ROTATION_ROTATE_270:
+ rotation = 270;
+ break;
+ default:
+ rotation = 0;
+ break;
+ }
+
+ xcb_randr_mode_info_t* currentMode = NULL;
+ xcb_randr_mode_info_t* preferredMode = NULL;
+
+ if (screenResources) {
+ xcb_randr_mode_info_iterator_t modesIterator = data->ffxcb_randr_get_screen_resources_current_modes_iterator(screenResources);
+
+ if (outputInfoReply->num_preferred > 0) {
+ preferredMode = modesIterator.data;
+ }
+
+ while (modesIterator.rem > 0) {
+ if (modesIterator.data->id == crtcInfoReply->mode) {
+ currentMode = modesIterator.data;
+ break;
+ }
+
+ data->ffxcb_randr_mode_info_next(&modesIterator);
+ }
+ }
+
+ FFDisplayResult* item = ffdsAppendDisplay(
+ data->result,
+ (uint32_t) (currentMode ? currentMode->width : crtcInfoReply->width),
+ (uint32_t) (currentMode ? currentMode->height : crtcInfoReply->height),
+ currentMode ? (double) currentMode->dot_clock / (double) ((uint32_t) currentMode->htotal * currentMode->vtotal) : 0,
+ dpi,
+ preferredMode ? (uint32_t) preferredMode->width : 0,
+ preferredMode ? (uint32_t) preferredMode->height : 0,
+ preferredMode ? (double) preferredMode->dot_clock / (double) ((uint32_t) preferredMode->htotal * preferredMode->vtotal) : 0,
+ rotation,
+ name,
+ displayType,
+ primary,
+ 0,
+ (uint32_t) outputInfoReply->mm_width,
+ (uint32_t) outputInfoReply->mm_height,
+ randrEmulation
+ ? (currentMode ? "xcb-randr-emu-mode" : "xcb-randr-emu-crtc")
+ : (currentMode ? "xcb-randr-mode" : "xcb-randr-crtc")
+
+ );
+ if (item) {
+ if (edidData && edidLength >= 128) {
+ item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek);
+ }
+ item->bitDepth = bitDepth;
+ if ((rotation == 90 || rotation == 180) && !randrEmulation) {
+ // In XWayland mode, width / height has been swapped out of box
+ uint32_t tmp = item->width;
+ item->width = item->height;
+ item->height = tmp;
+ }
+ }
+
+ return !!item;
+}
+
+static bool xcbRandrHandleMonitor(XcbRandrData* data, xcb_randr_monitor_info_t* monitor, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi) {
+ // for some reasons, we have to construct this our self
+ xcb_randr_output_iterator_t outputIterator = {
+ .index = 0,
+ .data = data->ffxcb_randr_monitor_info_outputs(monitor),
+ .rem = data->ffxcb_randr_monitor_info_outputs_length(monitor)
+ };
+
+ FF_AUTO_FREE xcb_get_atom_name_reply_t* nameReply = data->ffxcb_get_atom_name_reply(
+ data->connection,
+ data->ffxcb_get_atom_name(data->connection, monitor->name),
+ NULL);
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateNS(
+ (uint32_t) data->ffxcb_get_atom_name_name_length(nameReply),
+ data->ffxcb_get_atom_name_name(nameReply));
+ const FFDisplayType displayType = ffdsGetDisplayType(name.chars);
+
+ bool foundOutput = false;
+
+ while (outputIterator.rem > 0) {
+ if (xcbRandrHandleOutput(data, *outputIterator.data, &name, monitor->primary, displayType, screenResources, bitDepth, dpi)) {
+ foundOutput = true;
+ }
+ data->ffxcb_randr_output_next(&outputIterator);
+ }
+
+ if (foundOutput) {
+ return true;
+ }
+
+ FFDisplayResult* display = ffdsAppendDisplay(
+ data->result,
+ (uint32_t) monitor->width,
+ (uint32_t) monitor->height,
+ 0,
+ dpi,
+ 0,
+ 0,
+ 0,
+ 0,
+ &name,
+ displayType,
+ !!monitor->primary,
+ 0,
+ (uint32_t) monitor->width_in_millimeters,
+ (uint32_t) monitor->height_in_millimeters,
+ "xcb-randr-monitor");
+ if (display) {
+ display->bitDepth = bitDepth;
+ }
+ return !!display;
+}
+
+static bool xcbRandrHandleMonitors(XcbRandrData* data, xcb_screen_t* screen) {
+ xcb_randr_get_monitors_cookie_t monitorsCookie = data->ffxcb_randr_get_monitors(data->connection, screen->root, true);
+ FF_AUTO_FREE xcb_randr_get_monitors_reply_t* monitorsReply = data->ffxcb_randr_get_monitors_reply(data->connection, monitorsCookie, NULL);
+ if (monitorsReply == NULL) {
+ return false;
+ }
+
+ // Init screen resources. They are used to iterate over all modes. xcbRandrHandleMode checks for " == NULL", to fail as late as possible.
+ xcb_randr_get_screen_resources_current_cookie_t screenResourcesCookie = data->ffxcb_randr_get_screen_resources_current(data->connection, screen->root);
+ FF_AUTO_FREE struct xcb_randr_get_screen_resources_current_reply_t* screenResources = data->ffxcb_randr_get_screen_resources_current_reply(data->connection, screenResourcesCookie, NULL);
+
+ uint32_t dpi = 0;
+ FF_AUTO_FREE const char* resourceManager = xcbGetProperty(data, screen->root, "RESOURCE_MANAGER");
+ if (resourceManager) {
+ FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate();
+ if (ffParsePropLines(resourceManager, "Xft.dpi:", &dpiStr)) {
+ dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96);
+ }
+ }
+ uint8_t bitDepth = (uint8_t) (screen->root_depth / 3);
+
+ xcb_randr_monitor_info_iterator_t monitorInfoIterator = data->ffxcb_randr_get_monitors_monitors_iterator(monitorsReply);
+
+ bool foundMonitor = false;
+
+ while (monitorInfoIterator.rem > 0) {
+ if (xcbRandrHandleMonitor(data, monitorInfoIterator.data, screenResources, bitDepth, dpi)) {
+ foundMonitor = true;
+ }
+ data->ffxcb_randr_monitor_info_next(&monitorInfoIterator);
+ }
+
+ return foundMonitor;
+}
+
+static void xcbRandrHandleScreen(XcbRandrData* data, xcb_screen_t* screen) {
+ // With all the initialisation done, start the detection
+ if (xcbRandrHandleMonitors(data, screen)) {
+ return;
+ }
+
+ // If detetction failed, fallback to screen = monitor, like in the libxcb.so implementation
+ ffdsAppendDisplay(
+ data->result,
+ (uint32_t) screen->width_in_pixels,
+ (uint32_t) screen->height_in_pixels,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ NULL,
+ FF_DISPLAY_TYPE_UNKNOWN,
+ false,
+ (uint64_t) screen->root,
+ (uint32_t) screen->width_in_millimeters,
+ (uint32_t) screen->height_in_millimeters,
+ "xcb-randr-screen");
+}
+
+const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) {
+ FF_LIBRARY_LOAD_MESSAGE(xcbRandr, "libxcb-randr" FF_LIBRARY_EXTENSION, 1)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connect)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connection_has_error)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_get_setup)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_setup_roots_iterator)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_screen_next)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_disconnect)
+
+ XcbRandrData data;
+
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom_reply)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_reply)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value_length)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name_length)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_reply)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_setup)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor_length)
+
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_reply)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_modes_iterator)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_mode_info_next)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_reply)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_monitors_iterator)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_next)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs_length)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_output_next)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info_reply)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_reply)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data_length)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info_reply)
+
+ data.connection = ffxcb_connect(NULL, NULL);
+ if (ffxcb_connection_has_error(data.connection) > 0) {
+ ffxcb_disconnect(data.connection);
+ return "xcb_connect() failed";
+ }
+
+ data.result = result;
+
+ xcb_screen_iterator_t iterator = ffxcb_setup_roots_iterator(ffxcb_get_setup(data.connection));
+
+ if (iterator.rem > 0) {
+ xcbDetectWMfromEWMH(&data, iterator.data->root, result);
+ xcbFetchServerVendor(&data, result);
+ }
+
+ while (iterator.rem > 0) {
+ xcbRandrHandleScreen(&data, iterator.data);
+ ffxcb_screen_next(&iterator);
+ }
+
+ ffxcb_disconnect(data.connection);
+
+ // If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland.
+ if (result->wmProtocolName.length == 0) {
+ ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);
+ }
+
+ return NULL;
+}
+
+#else
+
+const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) {
+ // Do nothing. There are other implementations coming
+ FF_UNUSED(result)
+ return "Fastfetch was compiled without libxcb-randr support";
+}
+
+#endif
diff --git a/src/detection/displayserver/linux/xlib.c b/src/detection/displayserver/linux/xlib.c
new file mode 100644
index 0000000..2beffcc
--- /dev/null
+++ b/src/detection/displayserver/linux/xlib.c
@@ -0,0 +1,365 @@
+#include "displayserver_linux.h"
+
+#ifdef FF_HAVE_XRANDR
+
+ #include "common/library.h"
+ #include "common/properties.h"
+ #include "common/edidHelper.h"
+ #include "common/strutil.h"
+
+ #include <X11/extensions/Xrandr.h>
+ #include <X11/Xlib.h>
+
+typedef struct XrandrData {
+ FF_LIBRARY_SYMBOL(XInternAtom)
+ FF_LIBRARY_SYMBOL(XGetAtomName)
+ FF_LIBRARY_SYMBOL(XGetWindowProperty)
+ FF_LIBRARY_SYMBOL(XServerVendor)
+ FF_LIBRARY_SYMBOL(XFree)
+ FF_LIBRARY_SYMBOL(XRRGetMonitors)
+ FF_LIBRARY_SYMBOL(XRRGetScreenResourcesCurrent)
+ FF_LIBRARY_SYMBOL(XRRGetOutputInfo)
+ FF_LIBRARY_SYMBOL(XRRGetOutputProperty)
+ FF_LIBRARY_SYMBOL(XRRGetCrtcInfo)
+ FF_LIBRARY_SYMBOL(XRRFreeCrtcInfo)
+ FF_LIBRARY_SYMBOL(XRRFreeOutputInfo)
+ FF_LIBRARY_SYMBOL(XRRFreeScreenResources)
+ FF_LIBRARY_SYMBOL(XRRFreeMonitors)
+
+ // Init once
+ Display* display;
+ FFDisplayServerResult* result;
+} XrandrData;
+
+static unsigned char* x11GetProperty(XrandrData* data, Display* display, Window window, const char* request) {
+ Atom requestAtom = data->ffXInternAtom(display, request, False);
+ if (requestAtom == None) {
+ return NULL;
+ }
+
+ Atom actualType;
+ unsigned long unused;
+ unsigned char* result = NULL;
+
+ if (data->ffXGetWindowProperty(display, window, requestAtom, 0, 64, False, AnyPropertyType, &actualType, (int*) &unused, &unused, &unused, &result) != Success) {
+ return NULL;
+ }
+
+ return result;
+}
+
+static uint8_t* xrandrGetProperty(XrandrData* data, RROutput output, const char* name, uint32_t* bufSize) {
+ unsigned long size = 0;
+ uint8_t* result = NULL;
+ Atom atomEdid = data->ffXInternAtom(data->display, name, true);
+ if (atomEdid != None) {
+ int actual_format = 0;
+ unsigned long bytes_after = 0;
+ Atom actual_type = None;
+ if (data->ffXRRGetOutputProperty(data->display, output, atomEdid, 0, 100, false, false, AnyPropertyType, &actual_type, &actual_format, &size, &bytes_after, &result) == Success) {
+ if (size == 0) {
+ data->ffXFree(result);
+ } else {
+ if (bufSize) {
+ *bufSize = (uint32_t) size;
+ }
+ return result;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static void x11DetectWMFromEWMH(XrandrData* data, FFDisplayServerResult* result) {
+ if (result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND)) {
+ return;
+ }
+
+ Window* wmWindow = (Window*) x11GetProperty(data, data->display, DefaultRootWindow(data->display), "_NET_SUPPORTING_WM_CHECK");
+ if (wmWindow == NULL) {
+ return;
+ }
+
+ char* wmName = (char*) x11GetProperty(data, data->display, *wmWindow, "WM_NAME");
+ if (!ffStrSet(wmName)) {
+ wmName = (char*) x11GetProperty(data, data->display, *wmWindow, "_NET_WM_NAME");
+ }
+
+ if (ffStrSet(wmName)) {
+ ffStrbufSetS(&result->wmProcessName, wmName);
+ }
+
+ data->ffXFree(wmName);
+ data->ffXFree(wmWindow);
+}
+
+static void x11FetchServerVendor(XrandrData* data, FFDisplayServerResult* result) {
+ const char* serverVendor = data->ffXServerVendor(data->display);
+ if (serverVendor && !ffStrEquals(serverVendor, "The X.Org Foundation")) {
+ ffStrbufSetS(&result->wmProtocolName, serverVendor);
+ }
+}
+
+static bool xrandrHandleCrtc(XrandrData* data, XRROutputInfo* output, FFstrbuf* name, bool primary, FFDisplayType displayType, uint8_t* edidData, uint32_t edidLength, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi, bool randrEmulation) {
+ // We do the check here, because we want the best fallback display if this call failed
+ if (screenResources == NULL) {
+ return false;
+ }
+
+ XRRCrtcInfo* crtcInfo = data->ffXRRGetCrtcInfo(data->display, screenResources, output->crtc);
+ if (crtcInfo == NULL) {
+ return false;
+ }
+
+ uint32_t rotation;
+ switch (crtcInfo->rotation) {
+ case RR_Rotate_90:
+ rotation = 90;
+ break;
+ case RR_Rotate_180:
+ rotation = 180;
+ break;
+ case RR_Rotate_270:
+ rotation = 270;
+ break;
+ default:
+ rotation = 0;
+ break;
+ }
+
+ XRRModeInfo* currentMode = NULL;
+ for (int i = 0; i < screenResources->nmode; i++) {
+ if (screenResources->modes[i].id == crtcInfo->mode) {
+ currentMode = &screenResources->modes[i];
+ break;
+ }
+ }
+
+ XRRModeInfo* preferredMode = output->npreferred > 0 ? &screenResources->modes[0] : NULL;
+
+ FFDisplayResult* item = ffdsAppendDisplay(
+ data->result,
+ (uint32_t) (currentMode ? currentMode->width : crtcInfo->width),
+ (uint32_t) (currentMode ? currentMode->height : crtcInfo->height),
+ currentMode ? (double) currentMode->dotClock / (double) ((uint32_t) currentMode->hTotal * currentMode->vTotal) : 0,
+ dpi,
+ preferredMode ? (uint32_t) preferredMode->width : 0,
+ preferredMode ? (uint32_t) preferredMode->height : 0,
+ preferredMode ? (double) preferredMode->dotClock / (double) ((uint32_t) preferredMode->hTotal * preferredMode->vTotal) : 0,
+ rotation,
+ name,
+ displayType,
+ primary,
+ 0,
+ (uint32_t) output->mm_width,
+ (uint32_t) output->mm_height,
+ randrEmulation
+ ? (currentMode ? "xlib-randr-emu-mode" : "xlib-randr-emu-crtc")
+ : (currentMode ? "xlib-randr-mode" : "xlib-randr-crtc"));
+
+ if (item) {
+ if (edidLength) {
+ item->hdrStatus = ffEdidGetHdrCompatible(edidData, edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
+ ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek);
+ }
+ item->bitDepth = bitDepth;
+ if ((rotation == 90 || rotation == 180) && !randrEmulation) {
+ // In XWayland mode, width / height has been swapped out of box
+ uint32_t tmp = item->width;
+ item->width = item->height;
+ item->height = tmp;
+ }
+ }
+
+ data->ffXRRFreeCrtcInfo(crtcInfo);
+ return !!item;
+}
+
+static bool xrandrHandleOutput(XrandrData* data, RROutput output, FFstrbuf* name, bool primary, FFDisplayType displayType, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi) {
+ XRROutputInfo* outputInfo = data->ffXRRGetOutputInfo(data->display, screenResources, output);
+ if (outputInfo == NULL) {
+ return false;
+ }
+
+ uint32_t edidLength = 0;
+ uint8_t* edidData = xrandrGetProperty(data, output, RR_PROPERTY_RANDR_EDID, &edidLength);
+
+ if (edidLength >= 128) {
+ ffStrbufClear(name);
+ ffEdidGetName(edidData, name);
+ } else {
+ edidLength = 0;
+ }
+
+ uint8_t* randrEmulation = xrandrGetProperty(data, output, "RANDR Emulation", NULL);
+
+ bool res = xrandrHandleCrtc(data, outputInfo, name, primary, displayType, edidData, edidLength, screenResources, bitDepth, dpi, randrEmulation ? !!randrEmulation[0] : false);
+
+ if (edidData) {
+ data->ffXFree(edidData);
+ }
+ if (randrEmulation) {
+ data->ffXFree(randrEmulation);
+ }
+ data->ffXRRFreeOutputInfo(outputInfo);
+
+ return res;
+}
+
+static bool xrandrHandleMonitor(XrandrData* data, XRRMonitorInfo* monitorInfo, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi) {
+ bool foundOutput = false;
+ char* xname = data->ffXGetAtomName(data->display, monitorInfo->name);
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(xname);
+ data->ffXFree(xname);
+ FFDisplayType displayType = ffdsGetDisplayType(name.chars);
+ for (int i = 0; i < monitorInfo->noutput; i++) {
+ if (xrandrHandleOutput(data, monitorInfo->outputs[i], &name, monitorInfo->primary, displayType, screenResources, bitDepth, dpi)) {
+ foundOutput = true;
+ }
+ }
+
+ if (foundOutput) {
+ return true;
+ }
+
+ FFDisplayResult* display = ffdsAppendDisplay(
+ data->result,
+ (uint32_t) monitorInfo->width,
+ (uint32_t) monitorInfo->height,
+ 0,
+ dpi,
+ 0,
+ 0,
+ 0,
+ 0,
+ &name,
+ displayType,
+ !!monitorInfo->primary,
+ 0,
+ (uint32_t) monitorInfo->mwidth,
+ (uint32_t) monitorInfo->mheight,
+ "xlib-randr-monitor");
+ if (display) {
+ display->bitDepth = bitDepth;
+ }
+ return !!display;
+}
+
+static bool xrandrHandleMonitors(XrandrData* data, Screen* screen) {
+ int numberOfMonitors;
+ XRRMonitorInfo* monitorInfos = data->ffXRRGetMonitors(data->display, RootWindowOfScreen(screen), True, &numberOfMonitors);
+ if (monitorInfos == NULL) {
+ return false;
+ }
+
+ XRRScreenResources* screenResources = data->ffXRRGetScreenResourcesCurrent(data->display, RootWindowOfScreen(screen));
+
+ uint32_t dpi = 1;
+ char* resourceManager = (char*) x11GetProperty(data, data->display, screen->root, "RESOURCE_MANAGER");
+ if (resourceManager) {
+ FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate();
+ if (ffParsePropLines(resourceManager, "Xft.dpi:", &dpiStr)) {
+ dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96);
+ }
+ data->ffXFree(resourceManager);
+ }
+ uint8_t bitDepth = (uint8_t) (screen->root_depth / 3);
+
+ bool foundAMonitor = false;
+
+ for (int i = 0; i < numberOfMonitors; i++) {
+ if (xrandrHandleMonitor(data, &monitorInfos[i], screenResources, bitDepth, dpi)) {
+ foundAMonitor = true;
+ }
+ }
+
+ data->ffXRRFreeMonitors(monitorInfos);
+ data->ffXRRFreeScreenResources(screenResources);
+
+ return foundAMonitor;
+}
+
+static void xrandrHandleScreen(XrandrData* data, Screen* screen) {
+ if (xrandrHandleMonitors(data, screen)) {
+ return;
+ }
+
+ // Fallback to screen
+ ffdsAppendDisplay(
+ data->result,
+ (uint32_t) WidthOfScreen(screen),
+ (uint32_t) HeightOfScreen(screen),
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ NULL,
+ FF_DISPLAY_TYPE_UNKNOWN,
+ false,
+ RootWindowOfScreen(screen),
+ (uint32_t) WidthMMOfScreen(screen),
+ (uint32_t) HeightMMOfScreen(screen),
+ "xlib-randr-screen");
+}
+
+const char* ffdsConnectXrandr(FFDisplayServerResult* result) {
+ FF_LIBRARY_LOAD_MESSAGE(xrandr, "libXrandr" FF_LIBRARY_EXTENSION, 3)
+
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XOpenDisplay)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XCloseDisplay)
+
+ XrandrData data;
+
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XInternAtom);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetAtomName);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetWindowProperty);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XServerVendor);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XFree);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetMonitors);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetScreenResourcesCurrent);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputInfo);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputProperty);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetCrtcInfo);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeCrtcInfo);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeOutputInfo);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeScreenResources);
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeMonitors);
+
+ data.display = ffXOpenDisplay(NULL);
+ if (data.display == NULL) {
+ return "XOpenDisplay() failed";
+ }
+
+ if (ScreenCount(data.display) > 0) {
+ x11DetectWMFromEWMH(&data, result);
+ x11FetchServerVendor(&data, result);
+ }
+
+ data.result = result;
+
+ for (int i = 0; i < ScreenCount(data.display); i++) {
+ xrandrHandleScreen(&data, ScreenOfDisplay(data.display, i));
+ }
+
+ ffXCloseDisplay(data.display);
+
+ // If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland.
+ if (result->wmProtocolName.length == 0) {
+ ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11);
+ }
+
+ return NULL;
+}
+
+#else
+
+const char* ffdsConnectXrandr(FFDisplayServerResult* result) {
+ // Do nothing here. There are more x11 implementations to come.
+ FF_UNUSED(result);
+ return "Fastfetch was compiled without libXrandr support";
+}
+
+#endif // FF_HAVE_XRANDR