mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-27 23:30:47 +01:00
405 lines
16 KiB
C
405 lines
16 KiB
C
#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 "kde-output-device-v2-client-protocol.h"
|
|
#include "xdg-output-unstable-v1-client-protocol.h"
|
|
#include "wp-color-management-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, nullptr, 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, wl_output_interface.name)) {
|
|
wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_GLOBAL;
|
|
if (ffWaylandHandleGlobalOutput(wldata, registry, name, version) != nullptr) {
|
|
wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;
|
|
}
|
|
} else if ((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE) && ffStrEquals(interface, kde_output_device_registry_v2_interface.name)) {
|
|
wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_KDE_REGISTRY;
|
|
if (ffWaylandHandleKdeOutputRegistry(wldata, registry, name, version) != nullptr) {
|
|
wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;
|
|
}
|
|
} else if ((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_KDE_DEPRECATED) && ffStrEquals(interface, kde_output_device_v2_interface.name)) {
|
|
wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_KDE_DEPRECATED;
|
|
if (ffWaylandHandleKdeOutput(wldata, registry, name, version) != nullptr) {
|
|
wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE;
|
|
}
|
|
} 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);
|
|
} else if ((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_GLOBAL || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE) && ffStrEquals(interface, wp_color_manager_v1_interface.name)) {
|
|
ffWaylandHandleWpColor(wldata, registry, name, version);
|
|
}
|
|
}
|
|
|
|
[[maybe_unused]] static 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 == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
struct dirent* entry;
|
|
while ((entry = readdir(dirp)) != nullptr) {
|
|
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 && ffEdidIsValid(edidData, (uint32_t) edidLength)) {
|
|
ffEdidGetName(edidData, &wldata->edidName);
|
|
wldata->hdrSupported = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength);
|
|
ffEdidGetManufactureDate((const uint8_t*) edidData, &wldata->myear, &wldata->mweek);
|
|
ffEdidGetSerial((const uint8_t*) edidData, &wldata->serial);
|
|
if (wldata->preferredWidth == 0) {
|
|
uint32_t preferredWidth = 0, preferredHeight = 0;
|
|
double preferredRefreshRate = 0;
|
|
ffEdidGetPreferredResolutionAndRefreshRate(edidData, &preferredWidth, &preferredHeight, &preferredRefreshRate);
|
|
wldata->preferredWidth = (int32_t) preferredWidth;
|
|
wldata->preferredHeight = (int32_t) preferredHeight;
|
|
wldata->preferredRefreshRate = (int32_t) (preferredRefreshRate * 1000.);
|
|
}
|
|
wldata->hdrInfoAvailable = true;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ffWaylandOutputNameListener(void* data, [[maybe_unused]] void* output, const char* name) {
|
|
WaylandDisplay* display = data;
|
|
if (display->id) {
|
|
return;
|
|
}
|
|
|
|
display->type = ffdsGetDisplayType(name);
|
|
#if __linux__
|
|
if (!display->edidName.length && !display->parent->useMutter) {
|
|
matchDrmConnector(name, display);
|
|
}
|
|
#endif
|
|
display->id = ffWaylandGenerateIdFromName(name);
|
|
ffStrbufAppendS(&display->name, name);
|
|
}
|
|
|
|
void ffWaylandOutputDescriptionListener(void* data, [[maybe_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* ffWaylandWaitForDone(WaylandDisplay* display) {
|
|
int32_t timeout = instance.config.general.processingTimeout;
|
|
struct timespec ts;
|
|
if (timeout == 0) {
|
|
return "timeout is disabled";
|
|
} else if (timeout > 0) {
|
|
if (!display->parent->ffwl_display_dispatch_timeout) {
|
|
return "timeout is enabled, but the libwayland-client version is too old to support it";
|
|
}
|
|
ts.tv_sec = timeout / 1000;
|
|
ts.tv_nsec = (timeout % 1000) * 1000000;
|
|
}
|
|
|
|
WaylandData* wldata = display->parent;
|
|
|
|
// Some compositors emit the final output state asynchronously after the roundtrip callback (#2074)
|
|
while (!display->done) {
|
|
if (timeout > 0) {
|
|
if (wldata->ffwl_display_dispatch_timeout(wldata->display, &ts) < 0) {
|
|
return "wl_display_dispatch_timeout() failed";
|
|
}
|
|
} else if (timeout < 0) {
|
|
if (wldata->ffwl_display_dispatch(wldata->display) < 0) {
|
|
return "wl_display_dispatch() failed";
|
|
}
|
|
} else {
|
|
return "Timeout exceeded, but the compositor did not send a done event";
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const char* ffdsConnectWayland(FFDisplayServerResult* result) {
|
|
if (getenv("XDG_RUNTIME_DIR") == nullptr) {
|
|
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)
|
|
|
|
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)
|
|
|
|
if (instance.config.general.processingTimeout < 0) {
|
|
FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch)
|
|
} else if (instance.config.general.processingTimeout > 0) {
|
|
FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch_timeout)
|
|
}
|
|
|
|
data.display = ffwl_display_connect(nullptr);
|
|
if (data.display == nullptr) {
|
|
return "wl_display_connect returned nullptr";
|
|
}
|
|
|
|
waylandDetectWM(ffwl_display_get_fd(data.display), result);
|
|
|
|
#if defined(FF_HAVE_DBUS) && defined(__linux__)
|
|
// gnome-shell (Mutter) and cinnamon (Muffin) expose the monitor configuration via D-Bus,
|
|
// including the raw EDID. Prefer it over matching the wl_output name against the DRM connectors.
|
|
data.useMutter =
|
|
(ffStrbufEqualS(&result->wmProcessName, "gnome-shell") || ffStrbufEqualS(&result->wmProcessName, "cinnamon")) &&
|
|
ffdsMutterAvailable();
|
|
#endif
|
|
|
|
struct wl_proxy* registry = ffwl_proxy_marshal_constructor((struct wl_proxy*) data.display, WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, nullptr);
|
|
if (registry == nullptr) {
|
|
ffwl_display_disconnect(data.display);
|
|
return "wl_display_get_registry returned nullptr";
|
|
}
|
|
|
|
data.result = result;
|
|
|
|
struct wl_registry_listener registry_listener = {
|
|
.global = waylandGlobalAddListener,
|
|
.global_remove = (void*) ffUnused
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
if (data.wpColorManager) {
|
|
data.ffwl_proxy_destroy(data.wpColorManager);
|
|
}
|
|
|
|
data.ffwl_proxy_destroy(registry);
|
|
ffwl_display_disconnect(data.display);
|
|
|
|
bool primaryDetected = false;
|
|
#if defined(FF_HAVE_DBUS) && defined(__linux__)
|
|
if (data.useMutter) {
|
|
// Mutter / Muffin reports the primary monitor and the raw EDID directly
|
|
primaryDetected = ffdsConnectMutter(result);
|
|
}
|
|
#endif
|
|
|
|
// Fallback for compositors that do not expose the monitor configuration, or when it could not be read
|
|
if (!primaryDetected) {
|
|
const char* fileName = ffStrbufEqualS(&result->wmProcessName, "gnome-shell")
|
|
? "monitors.xml"
|
|
: ffStrbufEqualS(&result->wmProcessName, "cinnamon")
|
|
? "cinnamon-monitors.xml"
|
|
: nullptr;
|
|
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>");
|
|
uint64_t primaryDisplayId = ffWaylandGenerateIdFromName(name);
|
|
|
|
FF_LIST_FOR_EACH (FFDisplayResult, d, data.result->displays) {
|
|
if (d->id == 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 nullptr;
|
|
}
|
|
|
|
#else
|
|
|
|
const char* ffdsConnectWayland([[maybe_unused]] FFDisplayServerResult* result) {
|
|
return "Fastfetch was compiled without Wayland support";
|
|
}
|
|
|
|
#endif
|