mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-27 23:30:47 +01:00
Display (Android): adds richer information and adds detectWithGetprop back
This commit is contained in:
+9
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
Changes:
|
||||
* ImageMagick is no longer used for image logos on Windows, macOS and Android, and has been replaced by the platform image frameworks (WIC on Windows, ImageIO on macOS, AImageDecoder on Android). (Logo)
|
||||
* An image logo needs Android 11 (API 30), the release that introduced the platform image decoder, and is reported as an error on Android 10 and older.
|
||||
* ImageMagick 6 support is deprecated. It is kept only for old Debian and Ubuntu releases that don't have ImageMagick 7 available.
|
||||
* It is intended to be removed in a future release. Users are encouraged to upgrade to ImageMagick 7 when possible.
|
||||
|
||||
@@ -29,7 +30,7 @@ Features:
|
||||
* `--logo-animation-frame <N>` renders the Nth frame as a still image, and negative values count back from the end, so `-1` is the last frame. This works for the `sixel`, `kitty` and `chafa` logo types. Note that negative values can only be given in the JSON config, as the command line parser reads a leading `-` as another option.
|
||||
* The default is `1`, which renders a still image, so nothing changes for anyone who does not opt in.
|
||||
* The frames come from the platform image framework (WIC on Windows, ImageIO on macOS, AImageDecoder on Android, ImageMagick 7 on Linux). A single-frame GIF falls back to a still image.
|
||||
* On Android an animation needs Android 12 (API 31), which is where AImageDecoder gained the ability to decode past the first frame. AImageDecoder composes the frames itself; its API does not expose a repeat count, so an animation is reported as looping forever. A build with none of those, or one built with ImageMagick 6, reports an error instead of silently showing a still image.
|
||||
* On Android an animation needs Android 12 (API 31). The repeat count is not available there, so an animation is reported as looping forever. A build with no image decoder, or one built with ImageMagick 6, reports an error instead of silently showing a still image.
|
||||
* A terminal that supports the kitty graphics protocol but not its animation frames, such as Konsole, shows the first frame.
|
||||
* Added the CMake option `ENABLE_IMAGE_LOGO`, which defaults to `ON`. Configure with `-DENABLE_IMAGE_LOGO=OFF` to build fastfetch without any image logo support. (Logo)
|
||||
* Image logos are the only consumer of ImageMagick, chafa, and the embedded libsixel encoder, so none of the three is searched for at configure time, and no image decoding sources are compiled in.
|
||||
@@ -43,14 +44,19 @@ Features:
|
||||
* Added support for HarmonyOS, HarmonyOS NEXT, Flyme, JOYUI, SmartisanOS, realme UI, HydrogenOS, ZUI, ZUXOS, MyOS, NebulaAIOS, ObricUI, MiFavor, LineageOS, PixelExperience, EUI and 360 UI.
|
||||
* Added support for MagicUI 3.x, which stores a bare version number instead of a `MagicUI_x.y.z` string.
|
||||
* Added Samsung OneUI support (#2541)
|
||||
* It is read from system properties, so no particular Android version is required.
|
||||
* This is mostly untested due to lack of available devices running these ROMs. Please report any issues you encounter.
|
||||
* Improved Camera detection on Android (Camera, Android)
|
||||
* The camera list is now read from the camera2 NDK instead of `termux-api CameraInfo`, so the Termux:API app is no longer required and no subprocess is spawned.
|
||||
* This needs Android 7.0 (API 24), which is where the camera2 NDK was introduced.
|
||||
* Improved Battery detection on Android (Battery, Android)
|
||||
* The charge level and charging state are now read from the battery properties service over `/dev/binder` instead of `termux-api BatteryStatus`, so the Termux:API app is no longer required and no subprocess is spawned.
|
||||
* The service interface changed in Android 10, and the right request is picked at run time, so every Android release is covered.
|
||||
* Battery temperature, cycle count, manufacturer, model name, serial number and manufacture date need the `BATTERY_STATS` permission, which an app cannot obtain, so they are no longer reported.
|
||||
* Improved Display detection on Android when fastfetch runs as an app rather than from `adb shell`, where `dumpsys display` is not permitted. (Display, Android)
|
||||
* The displays are now read through the shell command interface of the display service, which needs no permission.
|
||||
* Improved Display detection on Android (Display, Android)
|
||||
* The displays are now read from the display service instead of a vendor property that only some Xiaomi devices set. The preferred mode, the physical size, the rotation, the manufacture date and the display id are now reported as well.
|
||||
* This needs Android 13 (API 33). On Android 12 and older only that vendor property is available to an app, and a device that does not set it reports no display.
|
||||
* The refresh rate is now the rate of the active display mode, and the HDR capability is read from the display itself, for every display rather than only for the built-in one.
|
||||
* Improved COSMIC detection (DE / WM, Linux)
|
||||
* The version is now read from the `COSMIC_VERSION` environment variable when it is set.
|
||||
* Improved accuracy and performance of process name detection in the Top module. (Top, macOS)
|
||||
|
||||
@@ -7,66 +7,95 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
static bool checkHdrStatus(FFDisplayResult* display) {
|
||||
static bool detectWithGetprop(FFDisplayServerResult* ds) {
|
||||
// Only for MiUI
|
||||
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;
|
||||
}
|
||||
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,
|
||||
nullptr,
|
||||
FF_DISPLAY_TYPE_BUILTIN,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"getprop");
|
||||
return !!display;
|
||||
}
|
||||
|
||||
display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void detectWithCmd(FFDisplayServerResult* ds) {
|
||||
// Unlike `dumpsys`, the shell command interface of the same service is not permission gated
|
||||
|
||||
// `cmd display get-displays` and `dumpsys display` print the same thing -- the `DisplayInfo` of every
|
||||
// display, one per line -- so one parser covers both and only the command and the marker in front of
|
||||
// each record differ:
|
||||
//
|
||||
// * `cmd display get-displays` needs no permission, which is what makes it usable for an app UID,
|
||||
// but the subcommand was only added to `DisplayManagerShellCommand` in Android 13. Android 11 and
|
||||
// 12 answer `Unknown command: get-displays` on stdout with exit code 255.
|
||||
// * `dumpsys display` covers every release, including the ones that predate `get-displays`, but it
|
||||
// is gated behind `android.permission.DUMP`, so it only answers for `adb shell` and root.
|
||||
//
|
||||
// The record layout has changed across releases, and every difference is accepted rather than version
|
||||
// checked:
|
||||
//
|
||||
// * The mode list is printed as `modes [...]` up to Android 14 and as `supportedModes [...]` from
|
||||
// Android 15 on, which also prints `appsSupportedModes [...]` right behind it. Both spellings are
|
||||
// searched for.
|
||||
// * `renderFrameRate` is printed from Android 15 on. Before that the active mode's fps is the only
|
||||
// refresh rate the dump carries. The active mode's fps is preferred even where it exists, see
|
||||
// the comment on `refreshRate` below.
|
||||
// * `displayGroupId` is printed from Android 12 on, the physical dpi behind `density` from
|
||||
// Android 11 on, and `isForceSdr` from Android 15 on.
|
||||
//
|
||||
// A record is one line, and the `DisplayInfo{` inside it is what gets parsed, so the `Display id 0: `
|
||||
// of the one command and the `mBaseDisplayInfo=` of the other are both skipped by the same code.
|
||||
static bool detectWithCommand(FFDisplayServerResult* ds, char* const argv[], const char* marker, const char* platformApi) {
|
||||
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
|
||||
FFProcessHandle handle;
|
||||
// `cmd` forwards its stdin to the service over binder, and the kernel rejects the whole
|
||||
// transaction when that fd is a terminal, which it is whenever fastfetch runs in a terminal.
|
||||
// Detaching the child from our stdin is only needed here, so the low level API is called
|
||||
// instead of `ffProcessAppendStdOut`.
|
||||
if (ffProcessSpawn((char*[]) {
|
||||
"/system/bin/cmd",
|
||||
"display",
|
||||
"get-displays",
|
||||
nullptr,
|
||||
},
|
||||
false,
|
||||
ffGetNullFD(),
|
||||
&handle) != nullptr) {
|
||||
return; // The shell command interface is not available on every Android version
|
||||
// Detaching the child from our stdin is only needed here, so the low level API is called instead
|
||||
// of `ffProcessAppendStdOut`.
|
||||
if (ffProcessSpawn(argv, false, ffGetNullFD(), &handle) != nullptr) {
|
||||
return false; // Neither command is available on every Android version
|
||||
}
|
||||
|
||||
if (ffProcessReadOutput(&handle, &buf) != nullptr || buf.length == 0) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
ffStrbufTrimRightSpace(&buf);
|
||||
|
||||
uint32_t index = 0;
|
||||
while ((index = ffStrbufNextIndexS(&buf, index, "Display id ")) < buf.length) {
|
||||
index += strlen("Display id ");
|
||||
while ((index = ffStrbufNextIndexS(&buf, index, marker)) < buf.length) {
|
||||
index += strlen(marker);
|
||||
|
||||
uint32_t nextIndex = ffStrbufNextIndexC(&buf, index, '\n');
|
||||
buf.chars[nextIndex] = '\0';
|
||||
const char* info = buf.chars + index;
|
||||
|
||||
// 0: DisplayInfo{"Builtin display", displayId 0, ..., real 1440 x 3168, ..., mode 2,
|
||||
// renderFrameRate 60.000004, ..., supportedModes [{id=2, width=1440, height=3168,
|
||||
// fps=60.000004, ...}], ..., type INTERNAL, ..., density 560 (560.0 x 560.0) dpi, ...}
|
||||
// renderFrameRate 60.000004, ..., defaultMode 4, ..., supportedModes [{id=2,
|
||||
// width=1440, height=3168, fps=60.000004, ...}], ..., hdrCapabilities
|
||||
// HdrCapabilities{mSupportedHdrTypes=[1, 2, 3, 4], ...}, isForceSdr false, ...,
|
||||
// rotation 0, ..., type INTERNAL, uniqueId "local:4630946557703207059", ...,
|
||||
// density 560 (560.0 x 560.0) dpi, ..., deviceProductInfo DeviceProductInfo{...,
|
||||
// manufactureDate=ManufactureDate{week=27, year=2006}, ...}, ...}
|
||||
const char* field = strstr(info, "DisplayInfo{\"");
|
||||
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(64);
|
||||
if (field) {
|
||||
@@ -77,36 +106,74 @@ static void detectWithCmd(FFDisplayServerResult* ds) {
|
||||
}
|
||||
}
|
||||
|
||||
// `real` is the size the display currently uses, which is smaller than the panel's when the
|
||||
// framework emulates a smaller display size
|
||||
unsigned width = 0, height = 0;
|
||||
if ((field = strstr(info, ", real ")) && sscanf(field, ", real %u x %u", &width, &height) < 2) {
|
||||
width = height = 0;
|
||||
}
|
||||
|
||||
double refreshRate = 0;
|
||||
if ((field = strstr(info, ", renderFrameRate ")) && sscanf(field, ", renderFrameRate %lf", &refreshRate) < 1) {
|
||||
refreshRate = 0;
|
||||
// `renderFrameRate` is printed from Android 15 on. It is documented as "a divisor of the
|
||||
// active mode refresh rate", so it is the rate the display is currently *rendering* at and
|
||||
// can be lower than the mode it is set to. It is therefore only used for a record whose
|
||||
// mode list can not be read, where a possibly divided rate still beats none.
|
||||
double renderFrameRate = 0;
|
||||
if ((field = strstr(info, ", renderFrameRate ")) && sscanf(field, ", renderFrameRate %lf", &renderFrameRate) < 1) {
|
||||
renderFrameRate = 0;
|
||||
}
|
||||
if (refreshRate <= 0) {
|
||||
// `renderFrameRate` is only printed since Android 11. Older builds expose the active mode
|
||||
// only, so its refresh rate has to be looked up in the list of supported modes.
|
||||
unsigned activeMode = 0;
|
||||
field = strstr(info, ", mode ");
|
||||
if (field && sscanf(field, ", mode %u", &activeMode) >= 1) {
|
||||
field = strstr(info, "supportedModes [");
|
||||
while (field && (field = strstr(field, "{id="))) {
|
||||
// {id=2, width=1440, height=3168, fps=60.000004, ...
|
||||
unsigned id = 0;
|
||||
double fps = 0;
|
||||
if (sscanf(field, "{id=%u, width=%*u, height=%*u, fps=%lf", &id, &fps) < 2) {
|
||||
break;
|
||||
}
|
||||
if (id == activeMode) {
|
||||
refreshRate = fps;
|
||||
break;
|
||||
}
|
||||
++field;
|
||||
}
|
||||
|
||||
unsigned activeMode = 0, defaultMode = 0;
|
||||
if ((field = strstr(info, ", mode ")) && sscanf(field, ", mode %u", &activeMode) < 1) {
|
||||
activeMode = 0;
|
||||
}
|
||||
if ((field = strstr(info, ", defaultMode ")) && sscanf(field, ", defaultMode %u", &defaultMode) < 1) {
|
||||
defaultMode = 0;
|
||||
}
|
||||
|
||||
// The modes are listed with their resolution in the natural orientation, which is also how
|
||||
// the preferred values are reported on the other platforms. `defaultMode` is the mode the
|
||||
// display itself prefers, so it carries the panel's native resolution.
|
||||
uint32_t preferredWidth = 0, preferredHeight = 0;
|
||||
double preferredRefreshRate = 0, activeModeRefreshRate = 0;
|
||||
field = strstr(info, ", supportedModes [");
|
||||
if (field == nullptr) {
|
||||
field = strstr(info, ", modes ["); // Android 14 and older
|
||||
}
|
||||
while (field && (field = strstr(field, "{id="))) {
|
||||
// {id=2, width=1440, height=3168, fps=60.000004, ...
|
||||
unsigned id = 0, modeWidth = 0, modeHeight = 0;
|
||||
double fps = 0;
|
||||
if (sscanf(field, "{id=%u, width=%u, height=%u, fps=%lf", &id, &modeWidth, &modeHeight, &fps) < 4) {
|
||||
break;
|
||||
}
|
||||
if (id == activeMode) {
|
||||
activeModeRefreshRate = fps;
|
||||
}
|
||||
if (id == defaultMode) {
|
||||
preferredWidth = modeWidth;
|
||||
preferredHeight = modeHeight;
|
||||
preferredRefreshRate = fps;
|
||||
}
|
||||
if (activeModeRefreshRate > 0 && preferredWidth > 0) {
|
||||
break; // Both are in, and the same list is printed a second time from Android 15 on
|
||||
}
|
||||
++field;
|
||||
}
|
||||
|
||||
// The nominal rate of the active mode, which is what `Display.getRefreshRate()` reports
|
||||
// (`refreshRateOverride` if it is set, the mode's own rate otherwise) and what every other
|
||||
// platform reports. `renderFrameRate` is deliberately not preferred: it is a render
|
||||
// cadence that follows the content rather than a property of the display, and it does not
|
||||
// exist before Android 15, so using it would make the reported rate change with the
|
||||
// Android version as well as with what is on screen.
|
||||
double refreshRate = activeModeRefreshRate;
|
||||
if (refreshRate <= 0) {
|
||||
refreshRate = renderFrameRate;
|
||||
}
|
||||
|
||||
unsigned rotation = 0;
|
||||
if ((field = strstr(info, ", rotation ")) && sscanf(field, ", rotation %u", &rotation) < 1) {
|
||||
rotation = 0;
|
||||
}
|
||||
|
||||
FFDisplayType type = FF_DISPLAY_TYPE_UNKNOWN;
|
||||
@@ -114,18 +181,63 @@ static void detectWithCmd(FFDisplayServerResult* ds) {
|
||||
field += strlen(", type ");
|
||||
if (ffStrStartsWith(field, "INTERNAL")) {
|
||||
type = FF_DISPLAY_TYPE_BUILTIN;
|
||||
} else if (ffStrStartsWith(field, "EXTERNAL")) {
|
||||
} else if (ffStrStartsWith(field, "EXTERNAL") || ffStrStartsWith(field, "WIFI")) {
|
||||
// A WIFI display is a wireless sink, which is as external as a wired one
|
||||
type = FF_DISPLAY_TYPE_EXTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned density = 0;
|
||||
if ((field = strstr(info, ", density ")) && sscanf(field, ", density %u", &density) < 1) {
|
||||
density = 0;
|
||||
double physicalXDpi = 0, physicalYDpi = 0;
|
||||
if ((field = strstr(info, ", density "))) {
|
||||
// `density 640 (501.0411 x 509.28604) dpi`, the physical dpi is only printed since
|
||||
// Android 11
|
||||
if (sscanf(field, ", density %u (%lf x %lf) dpi", &density, &physicalXDpi, &physicalYDpi) < 1) {
|
||||
density = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// The physical dpi describes the panel itself and does not change with the logical display
|
||||
// size, so the physical size has to be derived from the native resolution
|
||||
uint32_t physicalWidth = 0, physicalHeight = 0;
|
||||
if (physicalXDpi > 0) {
|
||||
physicalWidth = (uint32_t) ((preferredWidth ? preferredWidth : width) * 25.4 / physicalXDpi + 0.5);
|
||||
}
|
||||
if (physicalYDpi > 0) {
|
||||
physicalHeight = (uint32_t) ((preferredHeight ? preferredHeight : height) * 25.4 / physicalYDpi + 0.5);
|
||||
}
|
||||
|
||||
// `uniqueId` identifies the display across reboots, e.g. `local:4630946557703207059` on a
|
||||
// physical display and `virtual:...` on a virtual one
|
||||
uint64_t id = 0;
|
||||
if ((field = strstr(info, ", uniqueId \""))) {
|
||||
field += strlen(", uniqueId \"");
|
||||
const char* uniqueIdEnd = strchr(field, '"');
|
||||
const char* digits = uniqueIdEnd ? memchr(field, ':', (size_t) (uniqueIdEnd - field)) : nullptr;
|
||||
id = (uint64_t) strtoull(digits ? digits + 1 : field, nullptr, 10);
|
||||
}
|
||||
|
||||
uint16_t manufactureYear = 0, manufactureWeek = 0;
|
||||
if ((field = strstr(info, ", deviceProductInfo "))) {
|
||||
// `manufactureDate=ManufactureDate{week=27, year=2006}`, either field may be `null`
|
||||
const char* date = strstr(field, "manufactureDate=ManufactureDate{");
|
||||
unsigned year = 0, week = 0;
|
||||
if (date && sscanf(date + strlen("manufactureDate=ManufactureDate{"), "week=%u, year=%u", &week, &year) == 2) {
|
||||
manufactureYear = (uint16_t) year;
|
||||
manufactureWeek = (uint16_t) week;
|
||||
} else if ((field = strstr(field, ", modelYear=")) && sscanf(field, ", modelYear=%u", &year) == 1) {
|
||||
// A display reports either the date of manufacture or the model year
|
||||
manufactureYear = (uint16_t) year;
|
||||
}
|
||||
}
|
||||
|
||||
// `displayId` sits inside the record, not in front of it: `cmd` prints the id again in its
|
||||
// own prefix, but `dumpsys` prints only `mBaseDisplayInfo=`
|
||||
unsigned displayId = 0;
|
||||
bool primary = sscanf(info, "%u", &displayId) >= 1 && displayId == 0; // Display 0 is the default one
|
||||
if ((field = strstr(info, ", displayId ")) && sscanf(field, ", displayId %u", &displayId) < 1) {
|
||||
displayId = 0;
|
||||
}
|
||||
bool primary = displayId == 0; // Display 0 is the default one
|
||||
|
||||
// Android counts density in dpi with 160 as the 1x baseline, fastfetch uses 96
|
||||
FFDisplayResult* display = ffdsAppendDisplay(ds,
|
||||
@@ -133,23 +245,79 @@ static void detectWithCmd(FFDisplayServerResult* ds) {
|
||||
height,
|
||||
refreshRate,
|
||||
density * 96 / 160,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
preferredWidth,
|
||||
preferredHeight,
|
||||
preferredRefreshRate,
|
||||
rotation,
|
||||
&name,
|
||||
type,
|
||||
primary,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"cmd");
|
||||
id,
|
||||
physicalWidth,
|
||||
physicalHeight,
|
||||
platformApi);
|
||||
if (display) {
|
||||
display->hdrStatus = checkHdrStatus(display);
|
||||
display->manufactureYear = manufactureYear;
|
||||
display->manufactureWeek = manufactureWeek;
|
||||
|
||||
// Reported for every display, not only for the built-in one: `hdrCapabilities` is a
|
||||
// field of the `DisplayInfo` record itself, so it describes that display and nothing
|
||||
// else, and the other platforms report HDR per display too (EDID on Linux, the
|
||||
// advanced color info per target on Windows). An external display or a wireless sink
|
||||
// carries the field as well.
|
||||
//
|
||||
// `hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[1, 2, 3, 4], ...}` is printed
|
||||
// since Android 11, where an empty list means that the display can not do HDR at all.
|
||||
// The two fallbacks below it are device wide vendor properties, which is the price of
|
||||
// answering for a record that does not print the field.
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
field = strstr(info, "hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[");
|
||||
if (field) {
|
||||
field += strlen("hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[");
|
||||
display->hdrStatus = *field == ']' ? FF_DISPLAY_HDR_STATUS_UNSUPPORTED : FF_DISPLAY_HDR_STATUS_SUPPORTED;
|
||||
} else if (ffSettingsGetAndroidProperty("ro.surface_flinger.has_HDR_display", &buffer)) {
|
||||
display->hdrStatus = ffStrbufIgnCaseEqualS(&buffer, "true") ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
|
||||
} else {
|
||||
display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
if (display->hdrStatus == FF_DISPLAY_HDR_STATUS_SUPPORTED) {
|
||||
// `persist.sys.hdr_mode` is non-zero while HDR is turned on, and `isForceSdr
|
||||
// true` means that the framework disabled every HDR capability of this display.
|
||||
// Note that `ffSettingsGetAndroidProperty` appends, so the value needs its own
|
||||
// buffer.
|
||||
FF_STRBUF_AUTO_DESTROY hdrMode = ffStrbufCreate();
|
||||
if (ffSettingsGetAndroidProperty("persist.sys.hdr_mode", &hdrMode) &&
|
||||
ffStrbufToUInt(&hdrMode, 0) > 0 &&
|
||||
!strstr(info, ", isForceSdr true")) {
|
||||
display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index = nextIndex + 1;
|
||||
// The last display of the dump is not followed by a newline, so the loop must not step
|
||||
// past the end of the buffer (`ffStrbufNextIndexC` returns the length when it finds none)
|
||||
index = nextIndex < buf.length ? nextIndex + 1 : buf.length;
|
||||
}
|
||||
|
||||
// A command that produced no `DisplayInfo` record has to be reported as a failure: the caller
|
||||
// keys off this value to decide whether to try the other command, and on Android 12 and older
|
||||
// `dumpsys` is the only one that can still yield a display.
|
||||
return ds->displays.length > 0;
|
||||
}
|
||||
|
||||
static bool detectWithCmd(FFDisplayServerResult* ds) {
|
||||
return detectWithCommand(ds,
|
||||
(char*[]) { "/system/bin/cmd", "display", "get-displays", nullptr },
|
||||
"Display id ",
|
||||
"cmd");
|
||||
}
|
||||
|
||||
static bool detectWithDumpsys(FFDisplayServerResult* ds) {
|
||||
return detectWithCommand(ds,
|
||||
(char*[]) { "/system/bin/dumpsys", "display", nullptr },
|
||||
"mBaseDisplayInfo=",
|
||||
"dumpsys");
|
||||
}
|
||||
|
||||
// Several vendors embed the UI name and its version in `ro.build.display.id` without any
|
||||
@@ -493,7 +661,12 @@ void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
|
||||
ffStrbufSetStatic(&ds->wmPrettyName, "WindowManager"); // A system service managed by system_server
|
||||
ffStrbufSetStatic(&ds->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER);
|
||||
|
||||
detectWithCmd(ds);
|
||||
// `cmd` comes first because it needs no permission and therefore also works for an app UID.
|
||||
// `dumpsys` is the only route that answers on Android 12 and older, and only for `adb shell` and
|
||||
// root, and `getprop` is MiUI specific and the last resort.
|
||||
if (!detectWithCmd(ds) && !detectWithDumpsys(ds)) {
|
||||
detectWithGetprop(ds);
|
||||
}
|
||||
|
||||
detectDE(ds);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user