mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-29 11:10:59 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06957877a8 | |||
| 2f68ce7f74 | |||
| 408646df1c | |||
| ad62e3d1f5 | |||
| 197d1c6e5d | |||
| bfe74b8437 | |||
| bbb92543b0 | |||
| 99785f54dd |
@@ -1,3 +1,9 @@
|
|||||||
|
# Unreleased
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Improved uptime detection on SunOS via kstat. (#2674, Uptime, SunOS)
|
||||||
|
* Fixed substring formatting returning the remaining string when a negative end index is before the start of the string. (General)
|
||||||
|
|
||||||
# 2.70.0
|
# 2.70.0
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
|
|||||||
@@ -623,6 +623,7 @@ if(LINUX)
|
|||||||
src/detection/displayserver/linux/common.c
|
src/detection/displayserver/linux/common.c
|
||||||
src/detection/displayserver/linux/drm.c
|
src/detection/displayserver/linux/drm.c
|
||||||
src/detection/displayserver/linux/wayland/wayland.c
|
src/detection/displayserver/linux/wayland/wayland.c
|
||||||
|
src/detection/displayserver/linux/wayland/mutter.c
|
||||||
src/detection/displayserver/linux/wayland/global-output.c
|
src/detection/displayserver/linux/wayland/global-output.c
|
||||||
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
||||||
src/detection/displayserver/linux/wayland/wp-color-management-v1-protocol.c
|
src/detection/displayserver/linux/wayland/wp-color-management-v1-protocol.c
|
||||||
@@ -712,6 +713,7 @@ elseif(ANDROID)
|
|||||||
src/detection/diskio/diskio_linux.c
|
src/detection/diskio/diskio_linux.c
|
||||||
src/detection/displayserver/displayserver_android.c
|
src/detection/displayserver/displayserver_android.c
|
||||||
src/detection/displayserver/linux/wayland/wayland.c
|
src/detection/displayserver/linux/wayland/wayland.c
|
||||||
|
src/detection/displayserver/linux/wayland/mutter.c
|
||||||
src/detection/displayserver/linux/wayland/global-output.c
|
src/detection/displayserver/linux/wayland/global-output.c
|
||||||
src/detection/displayserver/linux/wayland/kde-output.c
|
src/detection/displayserver/linux/wayland/kde-output.c
|
||||||
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
||||||
@@ -800,6 +802,7 @@ elseif(FreeBSD)
|
|||||||
src/detection/displayserver/linux/common.c
|
src/detection/displayserver/linux/common.c
|
||||||
src/detection/displayserver/linux/drm.c
|
src/detection/displayserver/linux/drm.c
|
||||||
src/detection/displayserver/linux/wayland/wayland.c
|
src/detection/displayserver/linux/wayland/wayland.c
|
||||||
|
src/detection/displayserver/linux/wayland/mutter.c
|
||||||
src/detection/displayserver/linux/wayland/global-output.c
|
src/detection/displayserver/linux/wayland/global-output.c
|
||||||
src/detection/displayserver/linux/wayland/kde-output.c
|
src/detection/displayserver/linux/wayland/kde-output.c
|
||||||
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
||||||
@@ -898,6 +901,7 @@ elseif(NetBSD)
|
|||||||
src/detection/displayserver/linux/common.c
|
src/detection/displayserver/linux/common.c
|
||||||
src/detection/displayserver/linux/drm.c
|
src/detection/displayserver/linux/drm.c
|
||||||
src/detection/displayserver/linux/wayland/wayland.c
|
src/detection/displayserver/linux/wayland/wayland.c
|
||||||
|
src/detection/displayserver/linux/wayland/mutter.c
|
||||||
src/detection/displayserver/linux/wayland/global-output.c
|
src/detection/displayserver/linux/wayland/global-output.c
|
||||||
src/detection/displayserver/linux/wayland/kde-output.c
|
src/detection/displayserver/linux/wayland/kde-output.c
|
||||||
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
||||||
@@ -983,6 +987,7 @@ elseif(OpenBSD)
|
|||||||
src/detection/displayserver/linux/common.c
|
src/detection/displayserver/linux/common.c
|
||||||
src/detection/displayserver/linux/drm.c
|
src/detection/displayserver/linux/drm.c
|
||||||
src/detection/displayserver/linux/wayland/wayland.c
|
src/detection/displayserver/linux/wayland/wayland.c
|
||||||
|
src/detection/displayserver/linux/wayland/mutter.c
|
||||||
src/detection/displayserver/linux/wayland/global-output.c
|
src/detection/displayserver/linux/wayland/global-output.c
|
||||||
src/detection/displayserver/linux/wayland/kde-output.c
|
src/detection/displayserver/linux/wayland/kde-output.c
|
||||||
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
||||||
@@ -1243,6 +1248,7 @@ elseif(SunOS)
|
|||||||
src/detection/displayserver/linux/common.c
|
src/detection/displayserver/linux/common.c
|
||||||
src/detection/displayserver/linux/drm.c
|
src/detection/displayserver/linux/drm.c
|
||||||
src/detection/displayserver/linux/wayland/wayland.c
|
src/detection/displayserver/linux/wayland/wayland.c
|
||||||
|
src/detection/displayserver/linux/wayland/mutter.c
|
||||||
src/detection/displayserver/linux/wayland/global-output.c
|
src/detection/displayserver/linux/wayland/global-output.c
|
||||||
src/detection/displayserver/linux/wayland/kde-output.c
|
src/detection/displayserver/linux/wayland/kde-output.c
|
||||||
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
src/detection/displayserver/linux/wayland/wl-output-protocol.c
|
||||||
@@ -1402,6 +1408,7 @@ elseif(GNU)
|
|||||||
src/detection/diskio/diskio_nosupport.c
|
src/detection/diskio/diskio_nosupport.c
|
||||||
src/detection/displayserver/linux/displayserver_linux.c
|
src/detection/displayserver/linux/displayserver_linux.c
|
||||||
src/detection/displayserver/linux/wayland/wayland.c
|
src/detection/displayserver/linux/wayland/wayland.c
|
||||||
|
src/detection/displayserver/linux/wayland/mutter.c
|
||||||
src/detection/displayserver/linux/common.c
|
src/detection/displayserver/linux/common.c
|
||||||
src/detection/displayserver/linux/drm.c
|
src/detection/displayserver/linux/drm.c
|
||||||
src/detection/displayserver/linux/wmde.c
|
src/detection/displayserver/linux/wmde.c
|
||||||
|
|||||||
@@ -552,7 +552,7 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
|
|||||||
if (end < 0) {
|
if (end < 0) {
|
||||||
end = (int32_t) tempString.length + end;
|
end = (int32_t) tempString.length + end;
|
||||||
}
|
}
|
||||||
if ((uint32_t) end > tempString.length) {
|
if (end >= 0 && (uint32_t) end > tempString.length) {
|
||||||
end = (int32_t) tempString.length;
|
end = (int32_t) tempString.length;
|
||||||
}
|
}
|
||||||
if (end > start) {
|
if (end > start) {
|
||||||
|
|||||||
+36
-21
@@ -149,6 +149,32 @@ void restoreTerm(void) {
|
|||||||
tcsetattr(ftty, TCSAFLUSH, &oldTerm);
|
tcsetattr(ftty, TCSAFLUSH, &oldTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* waitForTerminalResponse(void) {
|
||||||
|
#ifndef __APPLE__
|
||||||
|
const int pollResult = poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS);
|
||||||
|
if (pollResult <= 0) {
|
||||||
|
FF_DEBUG("poll(/dev/tty) returned %d after %d ms: %s",
|
||||||
|
pollResult, FF_IO_TERM_RESP_WAIT_MS, pollResult == 0 ? "timed out" : strerror(errno));
|
||||||
|
return "poll(/dev/tty) timeout or failed";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
// On macOS, poll(/dev/tty) always returns immediately
|
||||||
|
// See also https://nathancraddock.com/blog/macos-dev-tty-polling/
|
||||||
|
fd_set rd;
|
||||||
|
FD_ZERO(&rd);
|
||||||
|
FD_SET(ftty, &rd);
|
||||||
|
const int selectResult = select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 });
|
||||||
|
if (selectResult <= 0) {
|
||||||
|
FF_DEBUG("select(/dev/tty) returned %d after %d ms: %s",
|
||||||
|
selectResult, FF_IO_TERM_RESP_WAIT_MS, selectResult == 0 ? "timed out" : strerror(errno));
|
||||||
|
return "select(/dev/tty) timeout or failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
|
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
|
||||||
if (ftty < 0) {
|
if (ftty < 0) {
|
||||||
ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC);
|
ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC);
|
||||||
@@ -174,28 +200,10 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
|
|||||||
ffWriteFDData(ftty, strlen(request), request);
|
ffWriteFDData(ftty, strlen(request), request);
|
||||||
|
|
||||||
// Give the terminal some time to respond
|
// Give the terminal some time to respond
|
||||||
#ifndef __APPLE__
|
const char* error = waitForTerminalResponse();
|
||||||
const int pollResult = poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS);
|
if (error) {
|
||||||
if (pollResult <= 0) {
|
return error;
|
||||||
FF_DEBUG("poll(/dev/tty) returned %d after %d ms: %s",
|
|
||||||
pollResult, FF_IO_TERM_RESP_WAIT_MS, pollResult == 0 ? "timed out" : strerror(errno));
|
|
||||||
return "poll(/dev/tty) timeout or failed";
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
{
|
|
||||||
// On macOS, poll(/dev/tty) always returns immediately
|
|
||||||
// See also https://nathancraddock.com/blog/macos-dev-tty-polling/
|
|
||||||
fd_set rd;
|
|
||||||
FD_ZERO(&rd);
|
|
||||||
FD_SET(ftty, &rd);
|
|
||||||
const int selectResult = select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 });
|
|
||||||
if (selectResult <= 0) {
|
|
||||||
FF_DEBUG("select(/dev/tty) returned %d after %d ms: %s",
|
|
||||||
selectResult, FF_IO_TERM_RESP_WAIT_MS, selectResult == 0 ? "timed out" : strerror(errno));
|
|
||||||
return "select(/dev/tty) timeout or failed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
size_t bytesRead = 0;
|
size_t bytesRead = 0;
|
||||||
@@ -204,6 +212,13 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
|
|||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// The response may arrive in several chunks; a terminal that sends an incomplete
|
||||||
|
// response would otherwise block `read` forever
|
||||||
|
if (bytesRead > 0 && (error = waitForTerminalResponse())) {
|
||||||
|
va_end(args);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1);
|
ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1);
|
||||||
|
|
||||||
if (nRead <= 0) {
|
if (nRead <= 0) {
|
||||||
|
|||||||
@@ -414,6 +414,35 @@ void ffListFilesRecursively(const char* path, bool pretty) {
|
|||||||
listFilesRecursively(folder.length, &folder, 0, nullptr, pretty);
|
listFilesRecursively(folder.length, &folder, 0, nullptr, pretty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* waitForTerminalResponse(HANDLE hInput) {
|
||||||
|
while (true) {
|
||||||
|
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 });
|
||||||
|
if (status != STATUS_WAIT_0) {
|
||||||
|
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status));
|
||||||
|
return "NtWaitForSingleObject() failed or timeout";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore all unexpected input events
|
||||||
|
INPUT_RECORD record;
|
||||||
|
DWORD len = 0;
|
||||||
|
if (!PeekConsoleInputW(hInput, &record, 1, &len)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key-up events carry no input for `ReadFile`; leaving one in the buffer would make the
|
||||||
|
// wait return while the next `ReadFile` still blocks
|
||||||
|
if (
|
||||||
|
record.EventType == KEY_EVENT &&
|
||||||
|
record.Event.KeyEvent.bKeyDown &&
|
||||||
|
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
|
||||||
|
record.Event.KeyEvent.uChar.UnicodeChar != L'\n') {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadConsoleInputW(hInput, &record, 1, &len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
|
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
|
||||||
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
|
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE;
|
FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE;
|
||||||
@@ -440,29 +469,10 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
|
|||||||
WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, nullptr);
|
WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
const char* error = waitForTerminalResponse(hInput);
|
||||||
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 });
|
if (error) {
|
||||||
if (status != STATUS_WAIT_0) {
|
|
||||||
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status));
|
|
||||||
SetConsoleMode(hInput, inputMode);
|
SetConsoleMode(hInput, inputMode);
|
||||||
return "NtWaitForSingleObject() failed or timeout";
|
return error;
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore all unexpected input events
|
|
||||||
INPUT_RECORD record;
|
|
||||||
DWORD len = 0;
|
|
||||||
if (!PeekConsoleInputW(hInput, &record, 1, &len)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
record.EventType == KEY_EVENT &&
|
|
||||||
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
|
|
||||||
record.Event.KeyEvent.uChar.UnicodeChar != L'\n') {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
ReadConsoleInputW(hInput, &record, 1, &len);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
@@ -472,6 +482,16 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
|
|||||||
uint32_t bytesRead = 0;
|
uint32_t bytesRead = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// The response may arrive in several chunks; a terminal that sends an incomplete
|
||||||
|
// response would otherwise block `ReadFile` forever
|
||||||
|
if (bytesRead > 0 && (error = waitForTerminalResponse(hInput))) {
|
||||||
|
va_end(args);
|
||||||
|
if (hasInputMode) {
|
||||||
|
SetConsoleMode(hInput, inputMode);
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
DWORD bytes = 0;
|
DWORD bytes = 0;
|
||||||
BOOL readOk = ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr);
|
BOOL readOk = ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr);
|
||||||
if (!readOk || bytes == 0) {
|
if (!readOk || bytes == 0) {
|
||||||
|
|||||||
@@ -0,0 +1,425 @@
|
|||||||
|
#include "wayland.h"
|
||||||
|
|
||||||
|
#if defined(FF_HAVE_WAYLAND) && defined(FF_HAVE_DBUS) && defined(__linux__)
|
||||||
|
|
||||||
|
#include "common/arrutil.h"
|
||||||
|
#include "common/dbus.h"
|
||||||
|
#include "common/debug.h"
|
||||||
|
#include "common/edidHelper.h"
|
||||||
|
#include "common/strutil.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// Read the display configuration of GNOME / Mutter and Cinnamon / Muffin, including the raw EDID
|
||||||
|
// data, instead of guessing the DRM connector by matching the wl_output name (#...).
|
||||||
|
// https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
|
||||||
|
|
||||||
|
// MetaMonitorColorMode
|
||||||
|
// https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/backends/meta-monitor-manager-private.h
|
||||||
|
#define FF_MUTTER_COLOR_MODE_BT2100 1
|
||||||
|
|
||||||
|
// Muffin (Cinnamon) exposes the very same interface as Mutter (GNOME), only the names differ
|
||||||
|
typedef struct FFMutterDisplayConfig {
|
||||||
|
const char* busName;
|
||||||
|
const char* objectPath;
|
||||||
|
const char* interface;
|
||||||
|
} FFMutterDisplayConfig;
|
||||||
|
|
||||||
|
static const FFMutterDisplayConfig mutterDisplayConfigs[] = {
|
||||||
|
{
|
||||||
|
.busName = "org.gnome.Mutter.DisplayConfig",
|
||||||
|
.objectPath = "/org/gnome/Mutter/DisplayConfig",
|
||||||
|
.interface = "org.gnome.Mutter.DisplayConfig",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.busName = "org.cinnamon.Muffin.DisplayConfig",
|
||||||
|
.objectPath = "/org/cinnamon/Muffin/DisplayConfig",
|
||||||
|
.interface = "org.cinnamon.Muffin.DisplayConfig",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline DBusMessage* mutterCall(FFDBusData* dbus, const FFMutterDisplayConfig* config, const char* method) {
|
||||||
|
return ffDBusGetMethodReply(dbus, config->busName, config->objectPath, config->interface, method, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the first display configuration interface that responds, or nullptr
|
||||||
|
static const FFMutterDisplayConfig* mutterFindConfig(FFDBusData* dbus) {
|
||||||
|
for (uint32_t i = 0; i < ARRAY_SIZE(mutterDisplayConfigs); ++i) {
|
||||||
|
DBusMessage* reply = mutterCall(dbus, &mutterDisplayConfigs[i], "GetCurrentState");
|
||||||
|
if (reply != nullptr) {
|
||||||
|
dbus->lib->ffdbus_message_unref(reply);
|
||||||
|
return &mutterDisplayConfigs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FFDisplayResult* mutterFindDisplay(FFDisplayServerResult* result, const char* connector) {
|
||||||
|
if (connector == nullptr || *connector == '\0') {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t id = ffWaylandGenerateIdFromName(connector);
|
||||||
|
FF_LIST_FOR_EACH (FFDisplayResult, display, result->displays) {
|
||||||
|
if (display->id == id) {
|
||||||
|
display->platformApi = "wayland-mutter";
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point `value` to the variant stored under `key` in the `a{sv}` dictionary `dict`
|
||||||
|
static bool mutterDictGet(FFDBusData* dbus, DBusMessageIter* dict, const char* key, DBusMessageIter* value) {
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(dict) != DBUS_TYPE_ARRAY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessageIter entries;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(dict, &entries);
|
||||||
|
|
||||||
|
while (dbus->lib->ffdbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_DICT_ENTRY) {
|
||||||
|
DBusMessageIter entry;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&entries, &entry);
|
||||||
|
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) {
|
||||||
|
const char* entryKey = nullptr;
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&entry, &entryKey);
|
||||||
|
if (entryKey && ffStrEquals(entryKey, key) && dbus->lib->ffdbus_message_iter_next(&entry)) {
|
||||||
|
*value = entry;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_next(&entries)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether the `au` array contained in the variant `variant` contains `needle`
|
||||||
|
static bool mutterVariantContainsUint32(FFDBusData* dbus, DBusMessageIter* variant, uint32_t needle) {
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(variant) != DBUS_TYPE_VARIANT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessageIter array;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(variant, &array);
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessageIter items;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&array, &items);
|
||||||
|
|
||||||
|
while (dbus->lib->ffdbus_message_iter_get_arg_type(&items) == DBUS_TYPE_UINT32) {
|
||||||
|
uint32_t item = 0;
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&items, &item);
|
||||||
|
if (item == needle) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_next(&items)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the `ay` byte array contained in the variant `variant` into `data`
|
||||||
|
static bool mutterVariantGetBytes(FFDBusData* dbus, DBusMessageIter* variant, uint8_t* data, uint32_t capacity, uint32_t* length) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(variant) == DBUS_TYPE_VARIANT) {
|
||||||
|
DBusMessageIter array;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(variant, &array);
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(&array) == DBUS_TYPE_ARRAY) {
|
||||||
|
DBusMessageIter items;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&array, &items);
|
||||||
|
while (dbus->lib->ffdbus_message_iter_get_arg_type(&items) == DBUS_TYPE_BYTE) {
|
||||||
|
uint8_t byte = 0;
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&items, &byte);
|
||||||
|
if (count < capacity) {
|
||||||
|
data[count] = byte;
|
||||||
|
}
|
||||||
|
++count;
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_next(&items)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*length = count;
|
||||||
|
return count > 0 && count <= capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mutterApplyEdid(FFDisplayResult* display, const uint8_t* edid, uint32_t length) {
|
||||||
|
FF_STRBUF_AUTO_DESTROY edidName = ffStrbufCreate();
|
||||||
|
if (ffEdidGetName(edid, &edidName) && edidName.length > 0) {
|
||||||
|
ffStrbufSet(&display->name, &edidName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display->serial.length == 0) {
|
||||||
|
ffEdidGetSerial(edid, &display->serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display->manufactureYear == 0) {
|
||||||
|
ffEdidGetManufactureDate(edid, &display->manufactureYear, &display->manufactureWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display->hdrStatus == FF_DISPLAY_HDR_STATUS_UNKNOWN) {
|
||||||
|
display->hdrStatus = ffEdidGetHdrCompatible(edid, length)
|
||||||
|
? FF_DISPLAY_HDR_STATUS_SUPPORTED
|
||||||
|
: FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display->preferredWidth == 0) {
|
||||||
|
uint32_t width = 0, height = 0;
|
||||||
|
double refreshRate = 0;
|
||||||
|
ffEdidGetPreferredResolutionAndRefreshRate(edid, &width, &height, &refreshRate);
|
||||||
|
display->preferredWidth = width;
|
||||||
|
display->preferredHeight = height;
|
||||||
|
display->preferredRefreshRate = refreshRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mutterApplyMonitorProperties(FFDBusData* dbus, DBusMessageIter* properties, FFDisplayResult* display) {
|
||||||
|
DBusMessageIter value;
|
||||||
|
|
||||||
|
// "is-builtin" is authoritative, unlike ffdsGetDisplayType() which guesses from the connector name
|
||||||
|
if (mutterDictGet(dbus, properties, "is-builtin", &value)) {
|
||||||
|
bool builtin = false;
|
||||||
|
if (ffDBusGetBool(dbus, &value, &builtin)) {
|
||||||
|
display->type = builtin ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hdrEnabled = false;
|
||||||
|
if (mutterDictGet(dbus, properties, "color-mode", &value)) {
|
||||||
|
uint64_t colorMode = 0;
|
||||||
|
hdrEnabled = ffDBusGetUint(dbus, &value, &colorMode) && colorMode == FF_MUTTER_COLOR_MODE_BT2100;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hdrSupported = false;
|
||||||
|
if (mutterDictGet(dbus, properties, "supported-color-modes", &value)) {
|
||||||
|
hdrSupported = mutterVariantContainsUint32(dbus, &value, FF_MUTTER_COLOR_MODE_BT2100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hdrEnabled) {
|
||||||
|
display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
|
||||||
|
} else if (hdrSupported && display->hdrStatus == FF_DISPLAY_HDR_STATUS_UNKNOWN) {
|
||||||
|
display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mutterParseCurrentState(FFDBusData* dbus, DBusMessage* reply, FFDisplayServerResult* result) {
|
||||||
|
DBusMessageIter root;
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_init(reply, &root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (u serial, a(monitors) monitors, a(logical_monitors) logical_monitors, a{sv} properties)
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_next(&root)) { // monitors
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(&root) == DBUS_TYPE_ARRAY) {
|
||||||
|
DBusMessageIter monitors;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&root, &monitors);
|
||||||
|
|
||||||
|
while (dbus->lib->ffdbus_message_iter_get_arg_type(&monitors) == DBUS_TYPE_STRUCT) {
|
||||||
|
DBusMessageIter monitor;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&monitors, &monitor);
|
||||||
|
|
||||||
|
// ((ssss) monitor_specs, a(siiddada{sv}) modes, a{sv} properties)
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(&monitor) == DBUS_TYPE_STRUCT) {
|
||||||
|
DBusMessageIter specs;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&monitor, &specs);
|
||||||
|
|
||||||
|
const char* connector = nullptr;
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(&specs) == DBUS_TYPE_STRING) {
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&specs, &connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
FFDisplayResult* display = mutterFindDisplay(result, connector);
|
||||||
|
if (display) {
|
||||||
|
// (connector, vendor, product, serial)
|
||||||
|
DBusMessageIter serialSpec = specs;
|
||||||
|
if (display->serial.length == 0 &&
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&serialSpec) && // vendor
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&serialSpec) && // product
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&serialSpec) && // serial
|
||||||
|
dbus->lib->ffdbus_message_iter_get_arg_type(&serialSpec) == DBUS_TYPE_STRING) {
|
||||||
|
const char* serial = nullptr;
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&serialSpec, &serial);
|
||||||
|
if (serial && !ffStrEquals(serial, "unknown")) {
|
||||||
|
ffStrbufSetS(&display->serial, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip `modes`, then read `properties`
|
||||||
|
DBusMessageIter properties = monitor;
|
||||||
|
if (dbus->lib->ffdbus_message_iter_next(&properties) &&
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&properties) &&
|
||||||
|
dbus->lib->ffdbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_ARRAY) {
|
||||||
|
mutterApplyMonitorProperties(dbus, &properties, display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&monitors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logical_monitors -> primary flag
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_next(&root) ||
|
||||||
|
dbus->lib->ffdbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessageIter logicalMonitors;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&root, &logicalMonitors);
|
||||||
|
|
||||||
|
while (dbus->lib->ffdbus_message_iter_get_arg_type(&logicalMonitors) == DBUS_TYPE_STRUCT) {
|
||||||
|
DBusMessageIter logicalMonitor;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&logicalMonitors, &logicalMonitor);
|
||||||
|
|
||||||
|
// (i x, i y, d scale, u transform, b primary, a(ssss) monitors, a{sv} properties)
|
||||||
|
int index = 0;
|
||||||
|
bool primary = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const int argType = dbus->lib->ffdbus_message_iter_get_arg_type(&logicalMonitor);
|
||||||
|
|
||||||
|
if (index == 4 && argType == DBUS_TYPE_BOOLEAN) {
|
||||||
|
dbus_bool_t value = 0;
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&logicalMonitor, &value);
|
||||||
|
primary = value != 0;
|
||||||
|
} else if (index == 5 && primary && argType == DBUS_TYPE_ARRAY) {
|
||||||
|
DBusMessageIter logicalMonitorMonitors;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&logicalMonitor, &logicalMonitorMonitors);
|
||||||
|
|
||||||
|
while (dbus->lib->ffdbus_message_iter_get_arg_type(&logicalMonitorMonitors) == DBUS_TYPE_STRUCT) {
|
||||||
|
DBusMessageIter specs;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&logicalMonitorMonitors, &specs);
|
||||||
|
if (dbus->lib->ffdbus_message_iter_get_arg_type(&specs) == DBUS_TYPE_STRING) {
|
||||||
|
const char* connector = nullptr;
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&specs, &connector);
|
||||||
|
FFDisplayResult* display = mutterFindDisplay(result, connector);
|
||||||
|
if (display) {
|
||||||
|
display->primary = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&logicalMonitorMonitors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++index;
|
||||||
|
} while (dbus->lib->ffdbus_message_iter_next(&logicalMonitor));
|
||||||
|
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&logicalMonitors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mutterParseResources(FFDBusData* dbus, DBusMessage* reply, FFDisplayServerResult* result) {
|
||||||
|
DBusMessageIter root;
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_init(reply, &root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (u serial, a(crtcs) crtcs, a(outputs) outputs, a(modes) modes, i max_screen_width, i max_screen_height)
|
||||||
|
DBusMessageIter outputs;
|
||||||
|
if (!dbus->lib->ffdbus_message_iter_next(&root) || // crtcs
|
||||||
|
!dbus->lib->ffdbus_message_iter_next(&root) || // outputs
|
||||||
|
dbus->lib->ffdbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&root, &outputs);
|
||||||
|
|
||||||
|
while (dbus->lib->ffdbus_message_iter_get_arg_type(&outputs) == DBUS_TYPE_STRUCT) {
|
||||||
|
DBusMessageIter output;
|
||||||
|
dbus->lib->ffdbus_message_iter_recurse(&outputs, &output);
|
||||||
|
|
||||||
|
// (u id, x output_id, i crtc_index, au crtcs, s name, au modes, au clones, a{sv} properties)
|
||||||
|
FFDisplayResult* display = nullptr;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const int argType = dbus->lib->ffdbus_message_iter_get_arg_type(&output);
|
||||||
|
|
||||||
|
if (index == 4 && argType == DBUS_TYPE_STRING) {
|
||||||
|
const char* connector = nullptr;
|
||||||
|
dbus->lib->ffdbus_message_iter_get_basic(&output, &connector);
|
||||||
|
display = mutterFindDisplay(result, connector);
|
||||||
|
} else if (index == 7 && display != nullptr && argType == DBUS_TYPE_ARRAY) {
|
||||||
|
DBusMessageIter value;
|
||||||
|
if (mutterDictGet(dbus, &output, "edid", &value)) {
|
||||||
|
uint8_t edid[1024];
|
||||||
|
uint32_t length = 0;
|
||||||
|
if (mutterVariantGetBytes(dbus, &value, edid, ARRAY_SIZE(edid), &length) && ffEdidIsValid(edid, length)) {
|
||||||
|
mutterApplyEdid(display, edid, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++index;
|
||||||
|
} while (dbus->lib->ffdbus_message_iter_next(&output));
|
||||||
|
|
||||||
|
dbus->lib->ffdbus_message_iter_next(&outputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether org.gnome.Mutter.DisplayConfig (GNOME) or org.cinnamon.Muffin.DisplayConfig (Cinnamon)
|
||||||
|
// is reachable. Used to decide if the wl_output name based DRM connector matching
|
||||||
|
// (matchDrmConnector) can be skipped.
|
||||||
|
bool ffdsMutterAvailable(void) {
|
||||||
|
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
|
||||||
|
if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutterFindConfig(&dbus) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich the displays detected from wl_output with the authoritative data reported by Mutter / Muffin.
|
||||||
|
// Returns true if the display configuration could be retrieved from D-Bus.
|
||||||
|
bool ffdsConnectMutter(FFDisplayServerResult* result) {
|
||||||
|
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
|
||||||
|
const char* error = ffDBusLoadData(DBUS_BUS_SESSION, &dbus);
|
||||||
|
if (error) {
|
||||||
|
FF_DEBUG("ffDBusLoadData() failed: %s", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FFMutterDisplayConfig* config = mutterFindConfig(&dbus);
|
||||||
|
if (config == nullptr) {
|
||||||
|
FF_DEBUG("No Mutter / Muffin DisplayConfig interface found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBusMessage* reply = mutterCall(&dbus, config, "GetCurrentState");
|
||||||
|
if (reply == nullptr) {
|
||||||
|
FF_DEBUG("GetCurrentState() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mutterParseCurrentState(&dbus, reply, result);
|
||||||
|
dbus.lib->ffdbus_message_unref(reply);
|
||||||
|
|
||||||
|
// GetCurrentState() does not expose the EDID data, so fall back to the legacy GetResources()
|
||||||
|
reply = mutterCall(&dbus, config, "GetResources");
|
||||||
|
if (reply != nullptr) {
|
||||||
|
mutterParseResources(&dbus, reply, result);
|
||||||
|
dbus.lib->ffdbus_message_unref(reply);
|
||||||
|
} else {
|
||||||
|
FF_DEBUG("GetResources() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -157,7 +157,7 @@ void ffWaylandOutputNameListener(void* data, [[maybe_unused]] void* output, cons
|
|||||||
|
|
||||||
display->type = ffdsGetDisplayType(name);
|
display->type = ffdsGetDisplayType(name);
|
||||||
#if __linux__
|
#if __linux__
|
||||||
if (!display->edidName.length) {
|
if (!display->edidName.length && !display->parent->useMutter) {
|
||||||
matchDrmConnector(name, display);
|
matchDrmConnector(name, display);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -282,6 +282,14 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) {
|
|||||||
|
|
||||||
waylandDetectWM(ffwl_display_get_fd(data.display), result);
|
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);
|
struct wl_proxy* registry = ffwl_proxy_marshal_constructor((struct wl_proxy*) data.display, WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, nullptr);
|
||||||
if (registry == nullptr) {
|
if (registry == nullptr) {
|
||||||
ffwl_display_disconnect(data.display);
|
ffwl_display_disconnect(data.display);
|
||||||
@@ -309,7 +317,16 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) {
|
|||||||
data.ffwl_proxy_destroy(registry);
|
data.ffwl_proxy_destroy(registry);
|
||||||
ffwl_display_disconnect(data.display);
|
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")
|
const char* fileName = ffStrbufEqualS(&result->wmProcessName, "gnome-shell")
|
||||||
? "monitors.xml"
|
? "monitors.xml"
|
||||||
: ffStrbufEqualS(&result->wmProcessName, "cinnamon")
|
: ffStrbufEqualS(&result->wmProcessName, "cinnamon")
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ typedef struct WaylandData {
|
|||||||
WaylandProtocolType protocolType;
|
WaylandProtocolType protocolType;
|
||||||
struct wl_proxy* zxdgOutputManager;
|
struct wl_proxy* zxdgOutputManager;
|
||||||
struct wl_proxy* wpColorManager;
|
struct wl_proxy* wpColorManager;
|
||||||
|
bool useMutter; // gnome-shell: read the display configuration from org.gnome.Mutter.DisplayConfig instead of guessing it
|
||||||
} WaylandData;
|
} WaylandData;
|
||||||
|
|
||||||
typedef struct WaylandDisplay {
|
typedef struct WaylandDisplay {
|
||||||
@@ -92,4 +93,9 @@ const char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registr
|
|||||||
const char* ffWaylandHandleZxdgOutput(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);
|
||||||
const char* ffWaylandHandleWpColor(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
|
const char* ffWaylandHandleWpColor(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
|
||||||
|
|
||||||
|
#if defined(FF_HAVE_DBUS) && defined(__linux__)
|
||||||
|
bool ffdsMutterAvailable(void);
|
||||||
|
bool ffdsConnectMutter(FFDisplayServerResult* result);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
#include "uptime.h"
|
#include "uptime.h"
|
||||||
|
#include "common/debug.h"
|
||||||
#include "common/time.h"
|
#include "common/time.h"
|
||||||
|
|
||||||
#include <utmpx.h>
|
#include <kstat.h>
|
||||||
|
|
||||||
|
static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
|
||||||
|
assert(pkc);
|
||||||
|
if (*pkc) {
|
||||||
|
kstat_close(*pkc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* ffDetectUptime(FFUptimeResult* result) {
|
const char* ffDetectUptime(FFUptimeResult* result) {
|
||||||
struct utmpx* ut;
|
[[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
|
||||||
|
if (!kc) {
|
||||||
|
FF_DEBUG("kstat_open() failed");
|
||||||
|
return "kstat_open() failed";
|
||||||
|
}
|
||||||
|
|
||||||
setutxent();
|
kstat_t* ks = kstat_lookup(kc, "unix", 0, "system_misc");
|
||||||
while (nullptr != (ut = getutxent())) {
|
if (!ks || kstat_read(kc, ks, nullptr) < 0) {
|
||||||
if (ut->ut_type == BOOT_TIME) {
|
FF_DEBUG("kstat_lookup(unix:0:system_misc) failed");
|
||||||
result->bootTime = (uint64_t) ut->ut_tv.tv_sec * 1000 + (uint64_t) ut->ut_tv.tv_usec / 1000000;
|
return "kstat_lookup(unix:0:system_misc) failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
kstat_named_t* bootTime = (kstat_named_t*) kstat_data_lookup(ks, "boot_time");
|
||||||
|
if (!bootTime) {
|
||||||
|
FF_DEBUG("kstat_data_lookup(boot_time) failed");
|
||||||
|
return "kstat_data_lookup(boot_time) failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
result->bootTime = (uint64_t) bootTime->value.ui32 * 1000;
|
||||||
result->uptime = ffTimeGetNow() - result->bootTime;
|
result->uptime = ffTimeGetNow() - result->bootTime;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endutxent();
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,15 @@ const char* ffDetectWifi(FFlist* result) {
|
|||||||
|
|
||||||
if (inf.ssid) { // https://developer.apple.com/forums/thread/732431
|
if (inf.ssid) { // https://developer.apple.com/forums/thread/732431
|
||||||
ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String);
|
ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String);
|
||||||
} else if (networkProfile.ssid) {
|
} else if (networkProfile.ssid) { // Dead in macOS 27.2
|
||||||
ffStrbufSetStatic(&item->conn.ssid, inf.configuration.networkProfiles.firstObject.ssid.UTF8String);
|
ffStrbufSetStatic(&item->conn.ssid, networkProfile.ssid.UTF8String);
|
||||||
} else {
|
} else {
|
||||||
ffStrbufSetStatic(&item->conn.ssid, "<redacted>"); // https://developer.apple.com/forums/thread/732431
|
ffStrbufSetStatic(&item->conn.ssid, "<redacted>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inf.bssid) {
|
if (inf.bssid) {
|
||||||
ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String);
|
ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String);
|
||||||
} else if (networkProfile.bssidList) {
|
} else if (networkProfile.bssidList) { // Dead in macOS 27.2
|
||||||
ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@"BSSID"] UTF8String]);
|
ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@"BSSID"] UTF8String]);
|
||||||
} else {
|
} else {
|
||||||
ffStrbufSetStatic(&item->conn.bssid, "<redacted>");
|
ffStrbufSetStatic(&item->conn.bssid, "<redacted>");
|
||||||
|
|||||||
@@ -85,6 +85,17 @@ static const FFlogo T[] = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef FASTFETCH_DATATEXT_LOGO_TINYCORE
|
||||||
|
// TinyCore
|
||||||
|
{
|
||||||
|
.names = { "TinyCore" },
|
||||||
|
.lines = FASTFETCH_DATATEXT_LOGO_TINYCORE,
|
||||||
|
.colors = {
|
||||||
|
FF_COLOR_FG_BLUE,
|
||||||
|
FF_COLOR_FG_LIGHT_WHITE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
#endif
|
||||||
#ifdef FASTFETCH_DATATEXT_LOGO_TORIZONCORE
|
#ifdef FASTFETCH_DATATEXT_LOGO_TORIZONCORE
|
||||||
// Torizon OS
|
// Torizon OS
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
$1 .-~~~~~~~-.
|
||||||
|
$1 .' $2tiny$1 '.
|
||||||
|
$1 / / \
|
||||||
|
$1 ; / ;
|
||||||
|
$1 | / |
|
||||||
|
$1 | / |
|
||||||
|
$1 ; / $2micro$1 ;
|
||||||
|
$1 \ / /
|
||||||
|
$1 '. .'
|
||||||
|
$1 '-.......-'
|
||||||
+4
-1
@@ -143,10 +143,13 @@ static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bo
|
|||||||
if (doColorReplacement && *data == '$') {
|
if (doColorReplacement && *data == '$') {
|
||||||
++data;
|
++data;
|
||||||
|
|
||||||
if (*data == '$' || *data == '\0') {
|
// `$$` is an escaped dollar; a `$` ending a line or the logo is a plain one
|
||||||
|
if (*data == '$' || *data == '\0' || *data == '\n' || *data == '\r') {
|
||||||
ffStrbufAppendC(&line, '$');
|
ffStrbufAppendC(&line, '$');
|
||||||
++lineWidth;
|
++lineWidth;
|
||||||
|
if (*data == '$') {
|
||||||
++data;
|
++data;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ void ffGenerateUptimeJsonConfig(FFUptimeOptions* options, yyjson_mut_doc* doc, y
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ffGenerateUptimeJsonResult([[maybe_unused]] FFUptimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
|
bool ffGenerateUptimeJsonResult([[maybe_unused]] FFUptimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
|
||||||
FFUptimeResult result;
|
FFUptimeResult result = {};
|
||||||
const char* error = ffDetectUptime(&result);
|
const char* error = ffDetectUptime(&result);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ int main(void) {
|
|||||||
VERIFY("output({1~10,10})", "12345 67890", "output()");
|
VERIFY("output({1~10,10})", "12345 67890", "output()");
|
||||||
VERIFY("output({1~10,5})", "12345 67890", "output()");
|
VERIFY("output({1~10,5})", "12345 67890", "output()");
|
||||||
VERIFY("output({1~3,-3})", "12345 67890", "output(45 67)");
|
VERIFY("output({1~3,-3})", "12345 67890", "output(45 67)");
|
||||||
|
VERIFY("output({1~0,-12})", "12345 67890", "output()");
|
||||||
VERIFY("output({1~-5,-3})", "12345 67890", "output(67)");
|
VERIFY("output({1~-5,-3})", "12345 67890", "output(67)");
|
||||||
VERIFY("output({1~-0,10})", "12345 67890", "output(12345 6789)");
|
VERIFY("output({1~-0,10})", "12345 67890", "output(12345 6789)");
|
||||||
VERIFY("output({1~-3,-5})", "12345 67890", "output()");
|
VERIFY("output({1~-3,-5})", "12345 67890", "output()");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "logo/logo.h"
|
#include "logo/logo.h"
|
||||||
#include "common/textModifier.h"
|
#include "common/textModifier.h"
|
||||||
|
#include "fastfetch.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -55,6 +56,15 @@ int main(void) {
|
|||||||
VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_TOP), "top") == 0);
|
VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_TOP), "top") == 0);
|
||||||
VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_RIGHT), "right") == 0);
|
VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_RIGHT), "right") == 0);
|
||||||
|
|
||||||
|
// A `$` ending a line or the logo is a plain dollar: it keeps the line break and the terminator.
|
||||||
|
instance.config.display.pipe = true;
|
||||||
|
ffLogoPrintChars("ab$\ncd", true);
|
||||||
|
VERIFY(instance.state.logoHeight == 1 && instance.state.logoWidth == 3);
|
||||||
|
ffLogoPrintChars("ab$", true);
|
||||||
|
VERIFY(instance.state.logoHeight == 0 && instance.state.logoWidth == 3);
|
||||||
|
ffLogoPrintChars("a$1b$$c", true);
|
||||||
|
VERIFY(instance.state.logoWidth == 4);
|
||||||
|
|
||||||
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
|
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user