master ohmu/fastfetch-openbsd / src / detection / displayserver / linux / wayland / kde-output.c
ohmu  ·  2026-10-15
  1#ifdef FF_HAVE_WAYLAND
  2
  3    #include "wayland.h"
  4    #include "kde-output-device-v2-client-protocol.h"
  5    #include "common/edidHelper.h"
  6    #include "common/base64.h"
  7
  8typedef struct WaylandKdeMode {
  9    int32_t width;
 10    int32_t height;
 11    int32_t refreshRate;
 12    bool preferred;
 13    struct kde_output_device_mode_v2* pMode;
 14} WaylandKdeMode;
 15
 16static void waylandKdeModeSizeListener(void* data, [[maybe_unused]] struct kde_output_device_mode_v2* _, int32_t width, int32_t height) {
 17    WaylandKdeMode* mode = (WaylandKdeMode*) data;
 18    mode->width = width;
 19    mode->height = height;
 20}
 21
 22static void waylandKdeModeRefreshListener(void* data, [[maybe_unused]] struct kde_output_device_mode_v2* _, int32_t rate) {
 23    WaylandKdeMode* mode = (WaylandKdeMode*) data;
 24    mode->refreshRate = rate;
 25}
 26
 27static void waylandKdeModePreferredListener(void* data, [[maybe_unused]] struct kde_output_device_mode_v2* _) {
 28    WaylandKdeMode* mode = (WaylandKdeMode*) data;
 29    mode->preferred = true;
 30}
 31
 32static const struct kde_output_device_mode_v2_listener modeListener = {
 33    .size = waylandKdeModeSizeListener,
 34    .refresh = waylandKdeModeRefreshListener,
 35    .preferred = waylandKdeModePreferredListener,
 36    .removed = (void*) ffUnused,
 37    .flags = (void*) ffUnused,
 38};
 39
 40static void waylandKdeModeListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) {
 41    WaylandDisplay* wldata = (WaylandDisplay*) data;
 42    if (!wldata->internal) {
 43        return;
 44    }
 45
 46    WaylandKdeMode* newMode = FF_LIST_ADD(WaylandKdeMode, *(FFlist*) wldata->internal);
 47    *newMode = (WaylandKdeMode) { .pMode = mode };
 48
 49    // Strangely, the listener is called only in this function, but not in `waylandKdeCurrentModeListener`
 50    wldata->parent->ffwl_proxy_add_listener((struct wl_proxy*) mode, (void (**)(void)) &modeListener, newMode);
 51}
 52
 53static void waylandKdeCurrentModeListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) {
 54    // waylandKdeModeListener is always run before this
 55    WaylandDisplay* wldata = (WaylandDisplay*) data;
 56    if (!wldata->internal) {
 57        return;
 58    }
 59
 60    bool foundCurrent = false, foundPreferred = false;
 61    FF_LIST_FOR_EACH (WaylandKdeMode, m, *(FFlist*) wldata->internal) {
 62        if (!foundCurrent && m->pMode == mode) {
 63            wldata->width = m->width;
 64            wldata->height = m->height;
 65            wldata->refreshRate = m->refreshRate;
 66            foundCurrent = true;
 67        }
 68        if (!foundPreferred && m->preferred) {
 69            wldata->preferredWidth = m->width;
 70            wldata->preferredHeight = m->height;
 71            wldata->preferredRefreshRate = m->refreshRate;
 72            foundPreferred = true;
 73        }
 74        if (foundCurrent && foundPreferred) {
 75            break;
 76        }
 77    }
 78}
 79
 80static void waylandKdeScaleListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, wl_fixed_t scale) {
 81    WaylandDisplay* wldata = (WaylandDisplay*) data;
 82    wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96;
 83}
 84
 85static void waylandKdeEdidListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, const char* raw) {
 86    if (!*raw) {
 87        return;
 88    }
 89    WaylandDisplay* wldata = (WaylandDisplay*) data;
 90    FF_STRBUF_AUTO_DESTROY b64 = ffStrbufCreateStatic(raw);
 91    FF_STRBUF_AUTO_DESTROY edid = ffBase64DecodeStrbuf(&b64);
 92    if (edid.length < 128) {
 93        return;
 94    }
 95    ffEdidGetName((const uint8_t*) edid.chars, &wldata->edidName);
 96    wldata->hdrSupported = ffEdidGetHdrCompatible((const uint8_t*) edid.chars, edid.length);
 97    ffEdidGetManufactureDate((const uint8_t*) edid.chars, &wldata->myear, &wldata->mweek);
 98    ffEdidGetSerial((const uint8_t*) edid.chars, &wldata->serial);
 99    wldata->hdrInfoAvailable = true;
100}
101
102static void waylandKdeEnabledListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, int32_t enabled) {
103    WaylandDisplay* wldata = (WaylandDisplay*) data;
104    if (!enabled) {
105        wldata->internal = nullptr;
106    }
107}
108
109static void waylandKdeGeometryListener(void* data,
110    [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2,
111    [[maybe_unused]] int32_t x,
112    [[maybe_unused]] int32_t y,
113    int32_t physical_width,
114    int32_t physical_height,
115    [[maybe_unused]] int32_t subpixel,
116    [[maybe_unused]] const char* make,
117    [[maybe_unused]] const char* model,
118    int32_t transform) {
119    WaylandDisplay* display = data;
120    display->physicalWidth = physical_width;
121    display->physicalHeight = physical_height;
122    display->transform = (enum wl_output_transform) transform;
123}
124
125static void waylandKdeNameListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, const char* name) {
126    WaylandDisplay* display = data;
127    display->type = ffdsGetDisplayType(name);
128    // As display->id is used as an internal identifier, we don't need it to be NUL terminated
129    strncpy((char*) &display->id, name, sizeof(display->id));
130
131    ffStrbufAppendS(&display->name, name);
132}
133
134static void waylandKdeHdrListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, uint32_t hdr_enabled) {
135    WaylandDisplay* display = data;
136    display->hdrEnabled = !!hdr_enabled;
137}
138
139static void waylandKdeMaxBitsPerColorListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, uint32_t max_bpc) {
140    WaylandDisplay* display = data;
141    display->bitDepth = (uint8_t) max_bpc;
142}
143
144static void waylandKdePriorityListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2, uint32_t priority) {
145    WaylandDisplay* display = data;
146    display->primary = priority == 1;
147}
148
149static void waylandKdeDoneListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2) {
150    WaylandDisplay* display = data;
151    display->done = true;
152}
153
154static struct kde_output_device_v2_listener outputListener = {
155    .geometry = waylandKdeGeometryListener,
156    .current_mode = waylandKdeCurrentModeListener,
157    .mode = waylandKdeModeListener,
158    .done = waylandKdeDoneListener,
159    .scale = waylandKdeScaleListener,
160    .edid = waylandKdeEdidListener,
161    .enabled = waylandKdeEnabledListener,
162    .uuid = (void*) ffUnused,
163    .serial_number = (void*) ffUnused,
164    .eisa_id = (void*) ffUnused,
165    .capabilities = (void*) ffUnused,
166    .overscan = (void*) ffUnused,
167    .vrr_policy = (void*) ffUnused,
168    .rgb_range = (void*) ffUnused,
169    .name = waylandKdeNameListener,
170    .high_dynamic_range = waylandKdeHdrListener,
171    .sdr_brightness = (void*) ffUnused,
172    .wide_color_gamut = (void*) ffUnused,
173    .auto_rotate_policy = (void*) ffUnused,
174    .icc_profile_path = (void*) ffUnused,
175    .brightness_metadata = (void*) ffUnused,
176    .brightness_overrides = (void*) ffUnused,
177    .sdr_gamut_wideness = (void*) ffUnused,
178    .color_profile_source = (void*) ffUnused,
179    .brightness = (void*) ffUnused,
180    .color_power_tradeoff = (void*) ffUnused,
181    .dimming = (void*) ffUnused,
182    .replication_source = (void*) ffUnused,
183    .ddc_ci_allowed = (void*) ffUnused,
184    .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener,
185    .max_bits_per_color_range = (void*) ffUnused,
186    .automatic_max_bits_per_color_limit = (void*) ffUnused,
187    .edr_policy = (void*) ffUnused,
188    .sharpness = (void*) ffUnused,
189    .priority = waylandKdePriorityListener,
190    .auto_brightness = (void*) ffUnused,
191    .removed = (void*) ffUnused,
192    .hdr_icc_profile_path = (void*) ffUnused,
193    .hdr_color_profile_source = (void*) ffUnused,
194    .abm_level = (void*) ffUnused,
195};
196
197static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy* output) {
198    FF_LIST_AUTO_DESTROY modes = ffListCreate();
199    WaylandDisplay display = {
200        .parent = wldata,
201        .transform = WL_OUTPUT_TRANSFORM_NORMAL,
202        .type = FF_DISPLAY_TYPE_UNKNOWN,
203        .name = ffStrbufCreate(),
204        .description = ffStrbufCreate(),
205        .edidName = ffStrbufCreate(),
206        .internal = &modes,
207    };
208
209    if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, &display) < 0) {
210        wldata->ffwl_proxy_destroy(output);
211        return "Failed to add listener to kde_output_device_v2";
212    }
213
214    if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
215        wldata->ffwl_proxy_destroy(output);
216        return "Failed to roundtrip kde_output_device_v2";
217    }
218    if (!display.done) {
219        const char* error = ffWaylandWaitForDone(&display);
220        if (error) {
221            wldata->ffwl_proxy_destroy(output);
222            return error;
223        }
224    }
225    // Destroy any mode proxies that were created during the listeners.
226    // wl proxies created for modes are not automatically freed by destroying
227    // the parent output proxy, so destroy them explicitly to avoid leaks.
228    FF_LIST_FOR_EACH (WaylandKdeMode, m, modes) {
229        if (m->pMode) {
230            wldata->ffwl_proxy_destroy((struct wl_proxy*) m->pMode);
231            m->pMode = nullptr;
232        }
233    }
234
235    wldata->ffwl_proxy_destroy(output);
236
237    if (display.width <= 0 || display.height <= 0 || !display.internal) {
238        return "Failed to get display information from kde_output_device_v2";
239    }
240
241    uint32_t rotation = ffWaylandHandleRotation(&display);
242
243    FFDisplayResult* item = ffdsAppendDisplay(wldata->result,
244        (uint32_t) display.width,
245        (uint32_t) display.height,
246        display.refreshRate / 1000.0,
247        display.dpi,
248        (uint32_t) display.preferredWidth,
249        (uint32_t) display.preferredHeight,
250        display.preferredRefreshRate / 1000.0,
251        rotation,
252        display.edidName.length
253            ? &display.edidName
254            : &display.name,
255        display.type,
256        display.primary,
257        display.id,
258        (uint32_t) display.physicalWidth,
259        (uint32_t) display.physicalHeight,
260        "wayland-kde");
261    if (item) {
262        if (display.hdrEnabled) {
263            item->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
264        } else if (display.hdrSupported) {
265            item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
266        } else if (display.hdrInfoAvailable) {
267            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
268        } else {
269            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
270        }
271
272        item->manufactureYear = display.myear;
273        item->manufactureWeek = display.mweek;
274        ffStrbufInitMove(&item->serial, &display.serial);
275        item->bitDepth = display.bitDepth;
276    }
277
278    ffStrbufDestroy(&display.description);
279    ffStrbufDestroy(&display.name);
280    ffStrbufDestroy(&display.edidName);
281
282    return nullptr;
283}
284
285const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
286    // TODO: remove this in future versions
287    uint32_t bindVersion = min(version, KDE_OUTPUT_DEVICE_V2_PRIORITY_SINCE_VERSION);
288    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, nullptr);
289    if (output == nullptr) {
290        return "Failed to create kde_output_device_v2";
291    }
292
293    return waylandKdeHandleOutput(wldata, output);
294}
295
296static void waylandKdeOutputListener(void* data, [[maybe_unused]] struct kde_output_device_registry_v2* kde_output_device_registry_v2, struct kde_output_device_v2* output) {
297    waylandKdeHandleOutput((WaylandData*) data, (struct wl_proxy*) output);
298}
299
300static struct kde_output_device_registry_v2_listener registryListener = {
301    .output = waylandKdeOutputListener,
302    .finished = (void*) ffUnused,
303};
304
305const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
306    uint32_t bindVersion = min(version, KDE_OUTPUT_DEVICE_REGISTRY_V2_OUTPUT_SINCE_VERSION);
307    struct wl_proxy* outputRegistry = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_registry_v2_interface, bindVersion, name, kde_output_device_registry_v2_interface.name, bindVersion, nullptr);
308    if (outputRegistry == nullptr) {
309        return "Failed to create kde_output_device_registry_v2";
310    }
311    if (wldata->ffwl_proxy_add_listener(outputRegistry, (void (**)(void)) &registryListener, wldata) < 0) {
312        wldata->ffwl_proxy_destroy(outputRegistry);
313        return "Failed to add listener to kde_output_device_registry_v2";
314    }
315    if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
316        wldata->ffwl_proxy_destroy(outputRegistry);
317        return "Failed to roundtrip kde_output_device_registry_v2";
318    }
319
320    wldata->ffwl_proxy_destroy(outputRegistry);
321    return nullptr;
322}
323
324#endif