diff options
Diffstat (limited to 'src/detection/displayserver/linux/wayland/zwlr-output.c')
| -rw-r--r-- | src/detection/displayserver/linux/wayland/zwlr-output.c | 212 |
1 files changed, 212 insertions, 0 deletions
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 |