diff options
Diffstat (limited to 'src/detection/displayserver')
26 files changed, 7527 insertions, 0 deletions
diff --git a/src/detection/displayserver/displayserver.c b/src/detection/displayserver/displayserver.c new file mode 100644 index 0000000..58c8c89 --- /dev/null +++ b/src/detection/displayserver/displayserver.c @@ -0,0 +1,67 @@ +#include "displayserver.h" + +FFDisplayResult* ffdsAppendDisplay( + FFDisplayServerResult* result, + uint32_t width, + uint32_t height, + double refreshRate, + uint32_t dpi, + uint32_t preferredWidth, + uint32_t preferredHeight, + double preferredRefreshRate, + uint32_t rotation, + FFstrbuf* name, + FFDisplayType type, + bool primary, + uint64_t id, + uint32_t physicalWidth, + uint32_t physicalHeight, + const char* platformApi) { + if (width == 0 || height == 0) { + return NULL; + } + + FFDisplayResult* display = FF_LIST_ADD(FFDisplayResult, result->displays); + display->width = width; + display->height = height; + display->refreshRate = refreshRate; + display->dpi = dpi ?: 96; // 0 means unknown + display->preferredWidth = preferredWidth; + display->preferredHeight = preferredHeight; + display->preferredRefreshRate = preferredRefreshRate; + display->rotation = rotation; + ffStrbufInitMove(&display->name, name); + display->type = type; + display->id = id; + display->physicalWidth = physicalWidth; + display->physicalHeight = physicalHeight; + display->primary = primary; + display->platformApi = platformApi; + + display->bitDepth = 0; + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; + display->manufactureYear = 0; + display->manufactureWeek = 0; + display->serial = 0; + display->drrStatus = FF_DISPLAY_DRR_STATUS_UNKNOWN; + + return display; +} + +void ffConnectDisplayServerImpl(FFDisplayServerResult* ds); + +const FFDisplayServerResult* ffConnectDisplayServer() { + static FFDisplayServerResult result; + static bool initialized = false; + if (!initialized) { + initialized = true; + ffStrbufInit(&result.wmProcessName); + ffStrbufInit(&result.wmPrettyName); + ffStrbufInit(&result.wmProtocolName); + ffStrbufInit(&result.deProcessName); + ffStrbufInit(&result.dePrettyName); + ffListInit(&result.displays); + ffConnectDisplayServerImpl(&result); + } + return &result; +} diff --git a/src/detection/displayserver/displayserver.h b/src/detection/displayserver/displayserver.h new file mode 100644 index 0000000..fd88f37 --- /dev/null +++ b/src/detection/displayserver/displayserver.h @@ -0,0 +1,123 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/display/option.h" + +#define FF_DE_PRETTY_PLASMA "KDE Plasma" +#define FF_DE_PRETTY_GNOME "GNOME" +#define FF_DE_PRETTY_GNOME_CLASSIC "GNOME Classic" +#define FF_DE_PRETTY_XFCE4 "Xfce4" +#define FF_DE_PRETTY_CINNAMON "Cinnamon" +#define FF_DE_PRETTY_MATE "Mate" +#define FF_DE_PRETTY_LXDE "LXDE" +#define FF_DE_PRETTY_LXQT "LXQt" +#define FF_DE_PRETTY_BUDGIE "Budgie" +#define FF_DE_PRETTY_CDE "CDE" +#define FF_DE_PRETTY_UNITY "Unity" +#define FF_DE_PRETTY_UKUI "UKUI" +#define FF_DE_PRETTY_NEBIDE "NebiDE" +#define FF_DE_PRETTY_ENLIGHTENMENT "Enlightenment" + +#define FF_WM_PRETTY_KWIN "KWin" +#define FF_WM_PRETTY_MUTTER "Mutter" +#define FF_WM_PRETTY_MUFFIN "Muffin" +#define FF_WM_PRETTY_MARCO "Marco" +#define FF_WM_PRETTY_XFWM4 "Xfwm4" +#define FF_WM_PRETTY_OPENBOX "Openbox" +#define FF_WM_PRETTY_I3 "i3" +#define FF_WM_PRETTY_HYPRLAND "Hyprland" +#define FF_WM_PRETTY_WAYFIRE "Wayfire" +#define FF_WM_PRETTY_SWAY "Sway" +#define FF_WM_PRETTY_BSPWM "bspwm" +#define FF_WM_PRETTY_DWM "dwm" +#define FF_WM_PRETTY_WESTON "Weston" +#define FF_WM_PRETTY_XMONAD "XMonad" +#define FF_WM_PRETTY_WSLG "WSLg" +#define FF_WM_PRETTY_TINYWM "TinyWM" +#define FF_WM_PRETTY_QTILE "Qtile" +#define FF_WM_PRETTY_HERBSTLUFTWM "herbstluftwm" +#define FF_WM_PRETTY_ICEWM "IceWM" +#define FF_WM_PRETTY_SPECTRWM "spectrwm" +#define FF_WM_PRETTY_DTWM "dtwm" +#define FF_WM_PRETTY_FVWM "fvwm" +#define FF_WM_PRETTY_CTWM "ctwm" +#define FF_WM_PRETTY_COSMIC_COMP "cosmic-comp" +#define FF_WM_PRETTY_RATPOISON "ratpoison" +#define FF_WM_PRETTY_ENLIGHTENMENT "Enlightenment" + +#define FF_WM_PROTOCOL_TTY "TTY" +#define FF_WM_PROTOCOL_X11 "X11" +#define FF_WM_PROTOCOL_WAYLAND "Wayland" +#define FF_WM_PROTOCOL_SURFACEFLINGER "SurfaceFlinger" + +typedef enum FF_A_PACKED FFDisplayType { + FF_DISPLAY_TYPE_UNKNOWN, + FF_DISPLAY_TYPE_BUILTIN, + FF_DISPLAY_TYPE_EXTERNAL, +} FFDisplayType; + +typedef enum FF_A_PACKED FFDisplayHdrStatus { + FF_DISPLAY_HDR_STATUS_UNKNOWN, + FF_DISPLAY_HDR_STATUS_UNSUPPORTED, + FF_DISPLAY_HDR_STATUS_SUPPORTED, + FF_DISPLAY_HDR_STATUS_ENABLED, +} FFDisplayHdrStatus; + +typedef enum FF_A_PACKED FFDisplayVrrStatus { + FF_DISPLAY_DRR_STATUS_UNKNOWN, + FF_DISPLAY_DRR_STATUS_DISABLED, + FF_DISPLAY_DRR_STATUS_ENABLED, +} FFDisplayVrrStatus; + +typedef struct FFDisplayResult { + uint32_t width; // in px + uint32_t height; // in px + double refreshRate; // in Hz + uint32_t dpi; // Base 96 + uint32_t preferredWidth; // in px + uint32_t preferredHeight; // in px + double preferredRefreshRate; // in Hz + FFstrbuf name; + FFDisplayType type; + uint32_t rotation; + uint64_t id; // platform dependent + uint32_t physicalWidth; // in mm + uint32_t physicalHeight; // in mm + bool primary; + const char* platformApi; + uint8_t bitDepth; + FFDisplayHdrStatus hdrStatus; + uint16_t manufactureYear; + uint16_t manufactureWeek; + uint32_t serial; + FFDisplayVrrStatus drrStatus; +} FFDisplayResult; + +typedef struct FFDisplayServerResult { + FFstrbuf wmProcessName; + FFstrbuf wmPrettyName; + FFstrbuf wmProtocolName; + FFstrbuf deProcessName; + FFstrbuf dePrettyName; + FFlist displays; // List of FFDisplayResult +} FFDisplayServerResult; + +const FFDisplayServerResult* ffConnectDisplayServer(); + +FFDisplayResult* ffdsAppendDisplay( + FFDisplayServerResult* result, + uint32_t width, + uint32_t height, + double refreshRate, + uint32_t dpi, + uint32_t preferredWidth, + uint32_t preferredHeight, + double preferredRefreshRate, + uint32_t rotation, + FFstrbuf* name, + FFDisplayType type, + bool primary, + uint64_t id, + uint32_t physicalWidth, + uint32_t physicalHeight, + const char* platformApi); diff --git a/src/detection/displayserver/displayserver_android.c b/src/detection/displayserver/displayserver_android.c new file mode 100644 index 0000000..9b2848a --- /dev/null +++ b/src/detection/displayserver/displayserver_android.c @@ -0,0 +1,209 @@ +#include "displayserver.h" +#include "common/settings.h" +#include "common/processing.h" +#include "linux/displayserver_linux.h" + +#include <math.h> + +static bool checkHdrStatus(FFDisplayResult* display) { + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + if (ffSettingsGetAndroidProperty("ro.surface_flinger.has_HDR_display", &buffer)) { + if (ffStrbufIgnCaseEqualS(&buffer, "true")) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; + + if (ffSettingsGetAndroidProperty("persist.sys.hdr_mode", &buffer) && + ffStrbufToUInt(&buffer, 0) > 0) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; + } + + return true; + } else { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + return true; + } + } + + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; + return false; +} + +static void detectWithDumpsys(FFDisplayServerResult* ds) { + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + if (ffProcessAppendStdOut(&buf, (char*[]) { + "/system/bin/dumpsys", + "display", + NULL, + }) != NULL || + buf.length == 0) { + return; // Only works in `adb shell`, or when rooted + } + + uint32_t index = 0; + while ((index = ffStrbufNextIndexS(&buf, index, "DisplayDeviceInfo")) < buf.length) { + index += strlen("DisplayDeviceInfo"); + uint32_t nextIndex = ffStrbufNextIndexC(&buf, index, '\n'); + buf.chars[nextIndex] = '\0'; + const char* info = buf.chars + index; + + // {"Builtin display": uniqueId="local:4630947134992368259", 1440 x 3200, modeId 2, defaultModeId 1, supportedModes [{id=1, width=1440, height=3200, fps=60.000004, alternativeRefreshRates=[24.000002, 30.000002, 40.0, 120.00001, 120.00001, 120.00001, 120.00001, 120.00001]}, + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(64); + unsigned width = 0, height = 0, modeId = 0; + double refreshRate = 0; + // {"Builtin display": uniqueId="local:4630947134992368259", 1440 x 3200, modeId 2 + int res = sscanf(info, "{\"%63[^\"]\":%*s%u x %u, modeId%u", name.chars, &width, &height, &modeId); + if (res >= 3) { + if (res == 4) { + ++info; // skip first '{' + while ((info = strchr(info, '{'))) { + ++info; + + unsigned id; + double fps; + // id=1, width=1440, height=3200, fps=60.000004, + if (sscanf(info, "id=%u, %*s%*s fps=%lf", &id, &fps) >= 2) { + if (id == modeId) { + refreshRate = fps; + break; + } + } else { + break; + } + } + } + + ffStrbufRecalculateLength(&name); + FFDisplayResult* display = ffdsAppendDisplay(ds, + (uint32_t) width, + (uint32_t) height, + refreshRate, + 0, + 0, + 0, + 0, + 0, + &name, + FF_DISPLAY_TYPE_UNKNOWN, + false, + 0, + 0, + 0, + "dumpsys"); + if (display) { + display->hdrStatus = checkHdrStatus(display); + } + } + + index = nextIndex + 1; + } +} + +static bool detectWithGetprop(FFDisplayServerResult* ds) { + // Only for MiUI + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + if (ffSettingsGetAndroidProperty("persist.sys.miui_resolution", &buffer) && + ffStrbufContainC(&buffer, ',')) { + // 1440,3200,560 => width,height,densityDpi + uint32_t width = (uint32_t) ffStrbufToUInt(&buffer, 0); + ffStrbufSubstrAfterFirstC(&buffer, ','); + uint32_t height = (uint32_t) ffStrbufToUInt(&buffer, 0); + ffStrbufSubstrAfterFirstC(&buffer, ','); + uint32_t dpi = (uint32_t) ffStrbufToUInt(&buffer, 0) * 96 / 160; + FFDisplayResult* display = ffdsAppendDisplay(ds, + width, + height, + 0, + dpi, + 0, + 0, + 0, + 0, + NULL, + FF_DISPLAY_TYPE_BUILTIN, + false, + 0, + 0, + 0, + "getprop"); + if (display) { + display->hdrStatus = checkHdrStatus(display); + } + return !!display; + } + + return false; +} + +static bool detectDE(FFDisplayServerResult* ds) { + if (ffSettingsGetAndroidProperty("ro.vivo.os.build.display.id", &ds->dePrettyName)) // OriginOS 6 + { + ffStrbufAppendC(&ds->dePrettyName, ' '); + ffSettingsGetAndroidProperty("ro.vivo.product.version", &ds->dePrettyName); // PD2505D_xxx + return true; + } + if (ffSettingsGetAndroidProperty("ro.build.version.magic", &ds->dePrettyName) || + ffSettingsGetAndroidProperty("ro.build.version.emui", &ds->dePrettyName)) { + ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' '); + return true; + } + if (ffSettingsGetAndroidProperty("ro.mi.os.version.name", &ds->dePrettyName)) { + // MiUI like + ffStrbufClear(&ds->dePrettyName); + ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName); // Detail version number + if (ffStrbufStartsWithS(&ds->dePrettyName, "OS")) { + ds->dePrettyName.chars[0] = 'S'; + ds->dePrettyName.chars[1] = ' '; + ffStrbufPrependS(&ds->dePrettyName, "HyperO"); + } else if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) { + ds->dePrettyName.chars[0] = ' '; + ffStrbufPrependS(&ds->dePrettyName, "MiUI"); + } else { + ffStrbufSetStatic(&ds->dePrettyName, "MiUI"); + } + return true; + } + if (ffSettingsGetAndroidProperty("ro.build.version.oplusrom", &ds->dePrettyName)) { + if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) { + ffStrbufSubstrAfter(&ds->dePrettyName, 0); + } + ffStrbufPrependS(&ds->dePrettyName, "ColorOS"); + return true; + } + if (ffSettingsGetAndroidProperty("ro.oxygen.version", &ds->dePrettyName)) { + ffStrbufPrependS(&ds->dePrettyName, "OxygenOS"); + return true; + } + if (ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName)) { + if (ffStrbufStartsWithS(&ds->dePrettyName, "RedMagicOS")) { + ffStrbufInsertNC(&ds->dePrettyName, strlen("RedMagicOS"), 1, ' '); + } + + // Google Pixel uses native Android + return true; + } + + return false; +} + +void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { + const char* error = ffdsConnectXcbRandr(ds); + if (error) { + error = ffdsConnectXrandr(ds); + } + if (!error) { + ffdsDetectWMDE(ds); + return; + } + + // https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager + ffStrbufSetStatic(&ds->wmProcessName, "system_server"); + ffStrbufSetStatic(&ds->wmPrettyName, "WindowManager"); // A system service managed by system_server + ffStrbufSetStatic(&ds->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER); + + if (!detectWithGetprop(ds)) { + detectWithDumpsys(ds); + } + + detectDE(ds); +} diff --git a/src/detection/displayserver/displayserver_apple.c b/src/detection/displayserver/displayserver_apple.c new file mode 100644 index 0000000..39ba604 --- /dev/null +++ b/src/detection/displayserver/displayserver_apple.c @@ -0,0 +1,197 @@ +#include "displayserver.h" +#include "common/apple/cf_helpers.h" +#include "common/strutil.h" +#include "common/edidHelper.h" +#include "detection/os/os.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <CoreGraphics/CGDirectDisplay.h> +#include <CoreVideo/CVDisplayLink.h> + +#ifdef MAC_OS_X_VERSION_10_15 +extern Boolean CoreDisplay_Display_SupportsHDRMode(CGDirectDisplayID display) FF_A_WEAK_IMPORT; +extern Boolean CoreDisplay_Display_IsHDRModeEnabled(CGDirectDisplayID display) FF_A_WEAK_IMPORT; +extern CFDictionaryRef CoreDisplay_DisplayCreateInfoDictionary(CGDirectDisplayID display) FF_A_WEAK_IMPORT; +#else + #include <IOKit/graphics/IOGraphicsLib.h> +#endif + +static void detectDisplays(FFDisplayServerResult* ds) { + CGDirectDisplayID screens[128]; + uint32_t screenCount; + if (CGGetOnlineDisplayList(ARRAY_SIZE(screens), screens, &screenCount) != kCGErrorSuccess) { + return; + } + + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + for (uint32_t i = 0; i < screenCount; i++) { + CGDirectDisplayID screen = screens[i]; + CGDisplayModeRef mode = CGDisplayCopyDisplayMode(screen); + if (mode) { + // https://github.com/glfw/glfw/commit/aab08712dd8142b642e2042e7b7ba563acd07a45 + double refreshRate = CGDisplayModeGetRefreshRate(mode); + + if (refreshRate == 0) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + CVDisplayLinkRef link; + if (CVDisplayLinkCreateWithCGDisplay(screen, &link) == kCVReturnSuccess) { + const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); + if (!(time.flags & kCVTimeIsIndefinite)) { + refreshRate = time.timeScale / (double) time.timeValue; // 59.97... + } + CVDisplayLinkRelease(link); + } +#pragma clang diagnostic pop + } + + ffStrbufClear(&buffer); + CFDictionaryRef FF_CFTYPE_AUTO_RELEASE displayInfo = NULL; +#ifdef MAC_OS_X_VERSION_10_15 + if (CoreDisplay_DisplayCreateInfoDictionary) { + displayInfo = CoreDisplay_DisplayCreateInfoDictionary(screen); + } +#else + { + io_service_t servicePort = CGDisplayIOServicePort(screen); + displayInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName); + } +#endif + + uint32_t physicalWidth = 0, physicalHeight = 0; + uint32_t preferredWidth = 0, preferredHeight = 0; + double preferredRefreshRate = 0; + + if (displayInfo) { + CFDictionaryRef productNames; + if (ffCfDictGetDict(displayInfo, CFSTR(kDisplayProductName), &productNames) == NULL) { + ffCfDictGetString(productNames, CFSTR("en_US"), &buffer); + } + + // CGDisplayScreenSize reports invalid result for external displays on old Intel MacBook Pro + CFDataRef edidRef = (CFDataRef) CFDictionaryGetValue(displayInfo, CFSTR(kIODisplayEDIDKey)); + if (edidRef && CFGetTypeID(edidRef) == CFDataGetTypeID()) { + const uint8_t* edidData = CFDataGetBytePtr(edidRef); + uint32_t edidLength = (uint32_t) CFDataGetLength(edidRef); + if (edidLength >= 128) { + ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight); + } + } + + if (!physicalWidth || !physicalHeight) { + if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayHorizontalImageSize), (int*) &physicalWidth) == NULL) { + ffCfDictGetInt(displayInfo, CFSTR(kDisplayVerticalImageSize), (int*) &physicalHeight); + } + } + + ffCfDictGetInt(displayInfo, CFSTR("kCGDisplayPixelWidth"), (int*) &preferredWidth); + ffCfDictGetInt(displayInfo, CFSTR("kCGDisplayPixelHeight"), (int*) &preferredHeight); + if (preferredWidth && preferredHeight) { + FF_CFTYPE_AUTO_RELEASE CFArrayRef allModes = CGDisplayCopyAllDisplayModes(screen, NULL); + if (allModes) { + for (CFIndex i = 0, count = CFArrayGetCount(allModes); i < count; i++) { + CGDisplayModeRef modeInfo = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, i); + if (CGDisplayModeGetPixelWidth(modeInfo) == preferredWidth && CGDisplayModeGetPixelHeight(modeInfo) == preferredHeight) { + double rr = CGDisplayModeGetRefreshRate(modeInfo); + if (rr > preferredRefreshRate) { + preferredRefreshRate = rr; + } + break; + } + } + } + } + } + + if ((!physicalWidth || !physicalHeight) && CGDisplayPrimaryDisplay(screen) == screen) // #1406 + { + CGSize size = CGDisplayScreenSize(screen); + physicalWidth = (uint32_t) (size.width + 0.5); + physicalHeight = (uint32_t) (size.height + 0.5); + } + + uint32_t pixelWidth = (uint32_t) CGDisplayModeGetPixelWidth(mode); + uint32_t pixelHeight = (uint32_t) CGDisplayModeGetPixelHeight(mode); + + FFDisplayResult* display = ffdsAppendDisplay(ds, + pixelWidth, + pixelHeight, + refreshRate, + pixelHeight * 96 / (uint32_t) CGDisplayModeGetHeight(mode), + preferredWidth, + preferredHeight, + preferredRefreshRate, + (uint32_t) CGDisplayRotation(screen), + &buffer, + CGDisplayIsBuiltin(screen) ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL, + CGDisplayIsMain(screen), + (uint64_t) screen, + physicalWidth, + physicalHeight, + "CoreGraphics"); + if (display) { +#ifndef MAC_OS_X_VERSION_10_11 + FF_CFTYPE_AUTO_RELEASE CFStringRef pe = CGDisplayModeCopyPixelEncoding(mode); + if (pe) { + display->bitDepth = (uint8_t) (CFStringGetLength(pe) - CFStringFind(pe, CFSTR("B"), 0).location); + } +#else + // https://stackoverflow.com/a/33519316/9976392 + // Also shitty, but better than parsing `CFCopyDescription(mode)` + CFDictionaryRef dict = (CFDictionaryRef) * ((int64_t*) mode + 2); + if (CFGetTypeID(dict) == CFDictionaryGetTypeID()) { + int32_t bitDepth; + ffCfDictGetInt(dict, kCGDisplayBitsPerSample, &bitDepth); + display->bitDepth = (uint8_t) bitDepth; + } +#endif + + if (display->type == FF_DISPLAY_TYPE_BUILTIN && displayInfo) { + display->hdrStatus = CFDictionaryContainsKey(displayInfo, CFSTR("ReferencePeakHDRLuminance")) + ? FF_DISPLAY_HDR_STATUS_SUPPORTED + : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + } +#ifdef MAC_OS_X_VERSION_10_15 + else if (CoreDisplay_Display_SupportsHDRMode) { + if (CoreDisplay_Display_SupportsHDRMode(screen)) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; + if (CoreDisplay_Display_IsHDRModeEnabled && CoreDisplay_Display_IsHDRModeEnabled(screen)) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; + } + } else { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + } + } +#endif + + display->serial = CGDisplaySerialNumber(screen); + + if (displayInfo) { + int value; + if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayYearOfManufacture), &value) == NULL) { + display->manufactureYear = (uint16_t) value; + } + if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayWeekOfManufacture), &value) == NULL) { + display->manufactureWeek = (uint16_t) value; + } + } + } + CGDisplayModeRelease(mode); + } + CGDisplayRelease(screen); + } +} + +void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { + { + FF_CFTYPE_AUTO_RELEASE CFMachPortRef port = CGWindowServerCreateServerPort(); + if (port) { + ffStrbufSetStatic(&ds->wmProcessName, "WindowServer"); + ffStrbufSetStatic(&ds->wmPrettyName, "Quartz Compositor"); + } + } + + detectDisplays(ds); +} diff --git a/src/detection/displayserver/displayserver_haiku.cpp b/src/detection/displayserver/displayserver_haiku.cpp new file mode 100644 index 0000000..c1ddaf6 --- /dev/null +++ b/src/detection/displayserver/displayserver_haiku.cpp @@ -0,0 +1,71 @@ +extern "C" { +#include "displayserver.h" +} + +#include <math.h> + +#include <Application.h> +#include <Screen.h> + +extern "C" void ffConnectDisplayServerImpl(FFDisplayServerResult* ds); + +static void detectDisplays(FFDisplayServerResult* ds) { + // We need a valid be_app to query the app_server here. + BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); + BScreen s{}; // default screen is the main one + bool main = true; + + do { + if (!s.IsValid()) { + continue; + } + + display_mode mode; + if (s.GetMode(&mode) != B_OK) { + continue; + } + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(128); + monitor_info monitor; + // WARNING: This is experimental new Haiku API + status_t err = s.GetMonitorInfo(&monitor); + if (err == B_OK) { + ffStrbufSetF(&name, "%s %s", monitor.vendor, monitor.name); + ffStrbufTrimRightSpace(&name); + } + + uint32_t width = (uint32_t) s.Frame().Width() + 1; + uint32_t height = (uint32_t) (uint32_t) s.Frame().Height() + 1; + FFDisplayResult* res = ffdsAppendDisplay(ds, + width, + height, + (double) mode.timing.pixel_clock * 1000 / (mode.timing.v_total * mode.timing.h_total), + 0, + 0, + 0, + 0, + 0, + &name, + FF_DISPLAY_TYPE_UNKNOWN, + main, + (uint64_t) s.ID().id, + 0, + 0, + "BScreen"); + if (err == B_OK) { + res->manufactureWeek = monitor.produced.week; + res->manufactureYear = monitor.produced.year; + } + main = false; + } while (s.SetToNext() == B_OK); + + return; +} + +void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { + ffStrbufSetStatic(&ds->wmProcessName, "app_server"); + ffStrbufSetStatic(&ds->wmPrettyName, "Application Server"); + ffStrbufSetStatic(&ds->dePrettyName, "Application Kit"); + + detectDisplays(ds); +} diff --git a/src/detection/displayserver/displayserver_windows.c b/src/detection/displayserver/displayserver_windows.c new file mode 100644 index 0000000..f92c144 --- /dev/null +++ b/src/detection/displayserver/displayserver_windows.c @@ -0,0 +1,219 @@ +#include "displayserver.h" +#include "common/edidHelper.h" +#include "common/windows/registry.h" +#include "common/windows/unicode.h" + +#include <windows.h> +#include <shellscalingapi.h> + +static inline void freeArgBuffer(FFArgBuffer* buffer) { + if (buffer->data) { + free(buffer->data); + } + buffer->data = NULL; + buffer->length = 0; +} +#define FF_AUTO_FREE_ARG_BUFFER FF_A_CLEANUP(freeArgBuffer) + +// http://undoc.airesoft.co.uk/user32.dll/IsThreadDesktopComposited.php +BOOL WINAPI IsThreadDesktopComposited(); +BOOL WINAPI GetDpiForMonitorInternal(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); + +static void detectDisplays(FFDisplayServerResult* ds) { + DISPLAYCONFIG_PATH_INFO paths[128]; + uint32_t pathCount = ARRAY_SIZE(paths); + DISPLAYCONFIG_MODE_INFO modes[256]; + uint32_t modeCount = ARRAY_SIZE(modes); + + if (QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, + &pathCount, + paths, + &modeCount, + modes, + NULL) == ERROR_SUCCESS) { + for (uint32_t i = 0; i < pathCount; ++i) { + const DISPLAYCONFIG_PATH_INFO* path = &paths[i]; + const DISPLAYCONFIG_SOURCE_MODE* sourceMode = &modes[path->sourceInfo.modeInfoIdx].sourceMode; + + FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); + uint32_t physicalWidth = 0, physicalHeight = 0; + + DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = { + .header = { + .type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME, + .size = sizeof(targetName), + .adapterId = path->targetInfo.adapterId, + .id = path->targetInfo.id, + }, + }; + FF_AUTO_FREE_ARG_BUFFER FFArgBuffer edid = {}; + if (DisplayConfigGetDeviceInfo(&targetName.header) == ERROR_SUCCESS) { + wchar_t regPath[256] = L"SYSTEM\\CurrentControlSet\\Enum"; + wchar_t* pRegPath = regPath + strlen("SYSTEM\\CurrentControlSet\\Enum"); + wchar_t* pDevPath = targetName.monitorDevicePath + strlen("\\\\?"); + while (*pDevPath && *pDevPath != L'{') { + if (*pDevPath == L'#') { + *pRegPath = L'\\'; + } else { + *pRegPath = *pDevPath; + } + ++pRegPath; + ++pDevPath; + assert(pRegPath < regPath + ARRAY_SIZE(regPath) + strlen("Device Parameters")); + } + wcscpy(pRegPath, L"Device Parameters"); + + FF_AUTO_CLOSE_FD HANDLE hKey = NULL; + if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regPath, &hKey, NULL) && + ffRegReadData(hKey, L"EDID", &edid, NULL) && + ffEdidIsValid(edid.data, edid.length)) { + ffEdidGetName(edid.data, &name); + ffEdidGetPhysicalSize(edid.data, &physicalWidth, &physicalHeight); + } else { + edid.length = 0; + if (targetName.flags.friendlyNameFromEdid) { + ffStrbufSetWS(&name, targetName.monitorFriendlyDeviceName); + } else { + ffStrbufSetWS(&name, targetName.monitorDevicePath); + ffStrbufSubstrAfterFirstC(&name, '#'); + ffStrbufSubstrBeforeFirstC(&name, '#'); + } + } + } + + uint32_t width = sourceMode->width; + uint32_t height = sourceMode->height; + uint32_t rotation; + switch (path->targetInfo.rotation) { + case DISPLAYCONFIG_ROTATION_ROTATE90: + rotation = 90; + break; + case DISPLAYCONFIG_ROTATION_ROTATE180: + rotation = 180; + break; + case DISPLAYCONFIG_ROTATION_ROTATE270: + rotation = 270; + break; + default: + rotation = 0; + break; + } + + DISPLAYCONFIG_TARGET_PREFERRED_MODE preferredMode = { + .header = { + .type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE, + .size = sizeof(preferredMode), + .adapterId = path->targetInfo.adapterId, + .id = path->targetInfo.id, + } + }; + double preferredRefreshRate = 0; + if (DisplayConfigGetDeviceInfo(&preferredMode.header) == ERROR_SUCCESS) { + DISPLAYCONFIG_RATIONAL freq = preferredMode.targetMode.targetVideoSignalInfo.vSyncFreq; + preferredRefreshRate = freq.Numerator / (double) freq.Denominator; + } + + uint32_t systemDpi = 0; + HMONITOR hMonitor = MonitorFromPoint(*(POINT*) &sourceMode->position, MONITOR_DEFAULTTONULL); + if (hMonitor) { + UINT ignored; + GetDpiForMonitorInternal(hMonitor, MDT_EFFECTIVE_DPI, &systemDpi, &ignored); + } + + if (systemDpi == 0) { + HDC hdc = GetDC(NULL); + systemDpi = (uint32_t) GetDeviceCaps(hdc, LOGPIXELSX); + if (systemDpi == 0) { + systemDpi = 96; + } + ReleaseDC(NULL, hdc); + } + + if (path->targetInfo.rotation == DISPLAYCONFIG_ROTATION_ROTATE90 || + path->targetInfo.rotation == DISPLAYCONFIG_ROTATION_ROTATE270) { + uint32_t temp = width; + width = height; + height = temp; + } + + FFDisplayResult* display = ffdsAppendDisplay(ds, + width, + height, + path->targetInfo.refreshRate.Numerator / (double) path->targetInfo.refreshRate.Denominator, + systemDpi, + preferredMode.width, + preferredMode.height, + preferredRefreshRate, + rotation, + &name, + path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER ? FF_DISPLAY_TYPE_UNKNOWN : path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL || path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED || path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED ? FF_DISPLAY_TYPE_BUILTIN + : FF_DISPLAY_TYPE_EXTERNAL, + sourceMode->position.x == 0 && sourceMode->position.y == 0, + (uintptr_t) hMonitor, + physicalWidth, + physicalHeight, + "GDI"); + + if (display) { + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 advColorInfo2 = { + .header = { + .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2, + .size = sizeof(advColorInfo2), + .adapterId = path->targetInfo.adapterId, + .id = path->targetInfo.id, + } + }; + if (DisplayConfigGetDeviceInfo(&advColorInfo2.header) == ERROR_SUCCESS) { + if (advColorInfo2.highDynamicRangeUserEnabled) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; + } else if (advColorInfo2.highDynamicRangeSupported) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; + } else { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + } + display->bitDepth = (uint8_t) advColorInfo2.bitsPerColorChannel; + } else { + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO advColorInfo = { + .header = { + .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, + .size = sizeof(advColorInfo), + .adapterId = path->targetInfo.adapterId, + .id = path->targetInfo.id, + } + }; + if (DisplayConfigGetDeviceInfo(&advColorInfo.header) == ERROR_SUCCESS) { + if (advColorInfo.advancedColorEnabled) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; + } else if (advColorInfo.advancedColorSupported) { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; + } else { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; + } + display->bitDepth = (uint8_t) advColorInfo.bitsPerColorChannel; + } else { + display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; + } + } + if (edid.length > 0) { + ffEdidGetSerialAndManufactureDate(edid.data, &display->serial, &display->manufactureYear, &display->manufactureWeek); + } + display->drrStatus = path->flags & DISPLAYCONFIG_PATH_BOOST_REFRESH_RATE ? FF_DISPLAY_DRR_STATUS_ENABLED : FF_DISPLAY_DRR_STATUS_DISABLED; + } + } + } +} + +void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { + if (IsThreadDesktopComposited()) { + ffStrbufSetStatic(&ds->wmProcessName, "dwm.exe"); + ffStrbufSetStatic(&ds->wmPrettyName, "Desktop Window Manager"); + } else { + // `explorer.exe` only provides a subset of WM functions, as well as the taskbar and desktop icons. + // While a window itself is drawn by kernel (GDI). Killing `explorer.exe` won't affect how windows are displayed generally. + ffStrbufSetStatic(&ds->wmProcessName, "explorer.exe"); + ffStrbufSetStatic(&ds->wmPrettyName, "Internal"); + } + + detectDisplays(ds); +} 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)) ®istry_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 |