master ohmu/fastfetch-openbsd / src / detection / displayserver / linux / wayland / global-output.c
ohmu  ·  2026-10-15
  1#ifdef FF_HAVE_WAYLAND
  2
  3    #include "wayland.h"
  4    #include "common/strutil.h"
  5    #include "xdg-output-unstable-v1-client-protocol.h"
  6    #include "wp-color-management-v1-client-protocol.h"
  7
  8static void waylandOutputModeListener(void* data, [[maybe_unused]] struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refreshRate) {
  9    WaylandDisplay* display = data;
 10
 11    if (flags & WL_OUTPUT_MODE_CURRENT) {
 12        display->width = width;
 13        display->height = height;
 14        display->refreshRate = refreshRate;
 15    }
 16    if (flags & WL_OUTPUT_MODE_PREFERRED) {
 17        display->preferredWidth = width;
 18        display->preferredHeight = height;
 19        display->preferredRefreshRate = refreshRate;
 20    }
 21}
 22
 23static void waylandOutputScaleListener(void* data, [[maybe_unused]] struct wl_output* output, int32_t scale) {
 24    WaylandDisplay* display = data;
 25    display->dpi = 96 * (uint32_t) scale;
 26}
 27
 28static void waylandOutputDoneListener(void* data, [[maybe_unused]] struct wl_output* output) {
 29    WaylandDisplay* display = data;
 30    display->done = true;
 31}
 32
 33static void waylandOutputGeometryListener(void* data,
 34    [[maybe_unused]] struct wl_output* output,
 35    [[maybe_unused]] int32_t x,
 36    [[maybe_unused]] int32_t y,
 37    int32_t physical_width,
 38    int32_t physical_height,
 39    [[maybe_unused]] int32_t subpixel,
 40    [[maybe_unused]] const char* make,
 41    [[maybe_unused]] const char* model,
 42    int32_t transform) {
 43    WaylandDisplay* display = data;
 44    display->physicalWidth = physical_width;
 45    display->physicalHeight = physical_height;
 46    display->transform = (enum wl_output_transform) transform;
 47}
 48
 49static void handleXdgLogicalSize(void* data, [[maybe_unused]] struct zxdg_output_v1* _, int32_t width, [[maybe_unused]] int32_t height) {
 50    WaylandDisplay* display = data;
 51    // Seems the values are only useful when ractional scale is enabled
 52    if (width < display->width) {
 53        display->dpi = (uint32_t) (display->width * 96 / width);
 54    }
 55}
 56
 57static struct wl_output_listener outputListener = {
 58    .geometry = waylandOutputGeometryListener,
 59    .mode = waylandOutputModeListener,
 60    .done = waylandOutputDoneListener,
 61    .scale = waylandOutputScaleListener,
 62    .name = (void*) ffWaylandOutputNameListener,
 63    .description = (void*) ffWaylandOutputDescriptionListener,
 64};
 65
 66static struct zxdg_output_v1_listener zxdgOutputListener = {
 67    .logical_position = (void*) ffUnused,
 68    .logical_size = handleXdgLogicalSize,
 69    .done = (void*) ffUnused,
 70    .name = (void*) ffWaylandOutputNameListener,
 71    .description = (void*) ffWaylandOutputDescriptionListener,
 72};
 73
 74static void handleWpTfNamed(void *data, [[maybe_unused]] struct wp_image_description_info_v1* wp_image_description_info_v1, uint32_t tf) {
 75    // KDE reports `gamma 2.2` even in HDR mode, but it should be handled in KDE specific path
 76    WaylandDisplay* display = data;
 77    switch (tf) {
 78        case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ:
 79        case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG:
 80            display->hdrInfoAvailable = true;
 81            display->hdrEnabled = true;
 82            break;
 83    }
 84}
 85
 86static const struct wp_image_description_info_v1_listener wpImageDescInfoListener = {
 87    .done = (void*) ffUnused,
 88    .icc_file = (void*) ffUnused,
 89    .primaries = (void*) ffUnused,
 90    .primaries_named = (void*) ffUnused,
 91    .tf_power = (void*) ffUnused,
 92    .tf_named = (void*) handleWpTfNamed,
 93    .luminances = (void*) ffUnused,
 94    .target_primaries = (void*) ffUnused,
 95    .target_luminance = (void*) ffUnused,
 96    .target_max_cll = (void*) ffUnused,
 97    .target_max_fall = (void*) ffUnused,
 98};
 99
100const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
101    const char* api = "wayland-base";
102    uint32_t bindVersion = min(version, WL_OUTPUT_DESCRIPTION_SINCE_VERSION);
103    struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &wl_output_interface, bindVersion, name, wl_output_interface.name, bindVersion, nullptr);
104    if (output == nullptr) {
105        return "Failed to create wl_output";
106    }
107
108    WaylandDisplay display = {
109        .parent = wldata,
110        .transform = WL_OUTPUT_TRANSFORM_NORMAL,
111        .type = FF_DISPLAY_TYPE_UNKNOWN,
112        .name = ffStrbufCreate(),
113        .description = ffStrbufCreate(),
114        .edidName = ffStrbufCreate(),
115    };
116
117    if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, &display) < 0) {
118        wldata->ffwl_proxy_destroy(output);
119        return "Failed to add listener to wl_output";
120    }
121    if (wldata->ffwl_display_roundtrip(wldata->display) < 0) {
122        wldata->ffwl_proxy_destroy(output);
123        return "Failed to roundtrip wl_output";
124    }
125    if (bindVersion >= WL_OUTPUT_DONE_SINCE_VERSION && !display.done) {
126        const char* error = ffWaylandWaitForDone(&display);
127        if (error) {
128            wldata->ffwl_proxy_destroy(output);
129            return error;
130        }
131    }
132
133    if (wldata->zxdgOutputManager) {
134        uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION);
135        struct wl_proxy* zxdgOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->zxdgOutputManager, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, bindVersion, nullptr, output);
136
137        if (zxdgOutput) {
138            wldata->ffwl_proxy_add_listener(zxdgOutput, (void (**)(void)) &zxdgOutputListener, &display);
139            wldata->ffwl_display_roundtrip(wldata->display);
140            wldata->ffwl_proxy_destroy(zxdgOutput);
141            api = "wayland-zxdg";
142        }
143    }
144
145    if (wldata->wpColorManager) {
146        uint32_t bindVersion = min(version, WP_COLOR_MANAGER_V1_GET_OUTPUT_SINCE_VERSION);
147        struct wl_proxy* wpColorOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->wpColorManager, WP_COLOR_MANAGER_V1_GET_OUTPUT, &wp_color_management_output_v1_interface, bindVersion, nullptr, output);
148
149        if (wpColorOutput) {
150            struct wl_proxy* wpImageDesc = wldata->ffwl_proxy_marshal_constructor_versioned(wpColorOutput, WP_COLOR_MANAGEMENT_OUTPUT_V1_GET_IMAGE_DESCRIPTION, &wp_image_description_v1_interface, bindVersion, nullptr);
151            if (wpImageDesc) {
152                struct wl_proxy* wpImageDescInfo = wldata->ffwl_proxy_marshal_constructor_versioned(wpImageDesc, WP_IMAGE_DESCRIPTION_V1_GET_INFORMATION, &wp_image_description_info_v1_interface, bindVersion, nullptr);
153                if (wpImageDescInfo) {
154                    wldata->ffwl_proxy_add_listener(wpImageDescInfo, (void (**)(void)) &wpImageDescInfoListener, &display);
155                    wldata->ffwl_display_roundtrip(wldata->display);
156                    wldata->ffwl_proxy_destroy(wpImageDescInfo);
157                }
158                wldata->ffwl_proxy_destroy(wpImageDesc);
159            }
160            wldata->ffwl_proxy_destroy(wpColorOutput);
161            wldata->ffwl_display_roundtrip(wldata->display);
162            api = api[strlen("wayland-")] == 'z' ? "wayland-zxdg+wpcolor" : "wayland-wpcolor";
163        }
164    }
165
166    wldata->ffwl_proxy_destroy(output);
167
168    if (display.width <= 0 || display.height <= 0) {
169        return "Failed to get display information from wl_output";
170    }
171
172    uint32_t rotation = ffWaylandHandleRotation(&display);
173
174    FFDisplayResult* item = ffdsAppendDisplay(wldata->result,
175        (uint32_t) display.width,
176        (uint32_t) display.height,
177        display.refreshRate / 1000.0,
178        display.dpi,
179        (uint32_t) display.preferredWidth,
180        (uint32_t) display.preferredHeight,
181        display.preferredRefreshRate / 1000.0,
182        rotation,
183        display.edidName.length
184            ? &display.edidName
185            // Try ignoring `eDP-1-unknown`, where `unknown` is localized
186            : display.description.length && !ffStrbufContain(&display.description, &display.name)
187            ? &display.description
188            : &display.name,
189        display.type,
190        false,
191        display.id,
192        (uint32_t) display.physicalWidth,
193        (uint32_t) display.physicalHeight,
194        api);
195    if (item) {
196        if (display.hdrEnabled) {
197            item->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
198        } else if (display.hdrSupported) {
199            item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
200        } else if (display.hdrInfoAvailable) {
201            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
202        } else {
203            item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
204        }
205
206        item->manufactureYear = display.myear;
207        item->manufactureWeek = display.mweek;
208        ffStrbufInitMove(&item->serial, &display.serial);
209    }
210
211    ffStrbufDestroy(&display.description);
212    ffStrbufDestroy(&display.name);
213    ffStrbufDestroy(&display.edidName);
214
215    return nullptr;
216}
217
218const char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
219    uint32_t bindVersion = min(version, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION);
220    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, nullptr);
221    if (manager == nullptr) {
222        return "Failed to create zxdg_output_manager_v1";
223    }
224
225    wldata->zxdgOutputManager = manager;
226
227    return nullptr;
228}
229
230const char* ffWaylandHandleWpColor(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) {
231    uint32_t bindVersion = min(version, WP_COLOR_MANAGER_V1_GET_OUTPUT_SINCE_VERSION);
232    struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &wp_color_manager_v1_interface, bindVersion, name, wp_color_manager_v1_interface.name, bindVersion, nullptr);
233    if (manager == nullptr) {
234        return "Failed to create wp_color_manager_v1";
235    }
236
237    wldata->wpColorManager = manager;
238
239    return nullptr;
240}
241
242#endif