summaryrefslogtreecommitdiffstats
path: root/src/detection/displayserver/linux/wayland/kde-output.c
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/wayland/kde-output.c
Add the files
Diffstat (limited to 'src/detection/displayserver/linux/wayland/kde-output.c')
-rw-r--r--src/detection/displayserver/linux/wayland/kde-output.c300
1 files changed, 300 insertions, 0 deletions
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