Skip to content

CRT: fix invisible gray color on direct-color terminals - #2079

Open
rahawii wants to merge 1 commit into
htop-dev:mainfrom
rahawii:fix-direct-gray-2078
Open

CRT: fix invisible gray color on direct-color terminals#2079
rahawii wants to merge 1 commit into
htop-dev:mainfrom
rahawii:fix-direct-gray-2078

Conversation

@rahawii

@rahawii rahawii commented Aug 19, 2026

Copy link
Copy Markdown

This PR fixes the issue where gray elements render as RGB(0,0,8) (effectively black) on direct-color terminals, causing them to disappear against a black background.

Changes

  • Replaced the legacy init_pair fallback for ColorIndexGrayBlack with init_extended_pair when COLORS >= 16777216. This makes the fix completely independent.
  • Passed 0x949494 (a highly visible 24-bit light gray) instead of the index 8.
  • Wrapped the extended API calls in preprocessor directives to ensure compatibility with older ncurses builds.

Visual Changes

  • Before: Gray elements (like bar shadows, memory cache lines, and process threads in standard schemes) were rendered as RGB(0,0,8), making them virtually invisible.
  • After: Elements now render correctly in a clear light gray (0x949494), restoring high contrast and readability for standard schemes on -direct terminals.
gray_fix

Fixes #2078

@coderabbitai coderabbitai Bot added the invalid 🗑️ This doesn't seem right label Aug 19, 2026
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6032fb10-f725-4fb2-8933-8f1ddda016de

📥 Commits

Reviewing files that changed from the base of the PR and between 0c649cf and 8cf4e50.

📒 Files selected for processing (1)
  • CRT.c

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

CRT_setColors now initializes the gray-black pair with an extended foreground color when init_extended_pair is available and the terminal supports at least 16.7 million colors. It selects 0x5F5F5F for light-terminal and black-on-white schemes, and 0x949494 otherwise. Standard-color fallback behavior remains. configure.ac detects init_extended_pair.

Assessment against linked issues

Objective Addressed Explanation
Use a real gray for gray elements on direct-color terminals while retaining compatibility with standard terminals [#2078]

Possibly related PRs

  • htop-dev/htop#2072: Both changes update CRT_setColors to use ncurses extended-color pair APIs.

Poem

Direct colors now show gray,
Extended pairs set the tone.
Standard terminals keep fallback,
Gray-black no longer looks like stone.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 10aa91cb-2613-4b3a-93f9-6ea7726dc7d0

📥 Commits

Reviewing files that changed from the base of the PR and between 6f33ddd and 2c42504.

📒 Files selected for processing (3)
  • CRT.c
  • CRT.h
  • ColorsPanel.c

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread ColorsPanel.c Outdated
"Black Night",
"Broken Gray",
"Nord",
"Direct",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the selected scheme consistent with the applied scheme.

On a terminal without RGB support, this new entry can be selected and persisted as COLORSCHEME_DIRECT. CRT_setColors() then applies COLORSCHEME_DEFAULT at CRT.c Line 1479. The panel check state and saved setting can therefore disagree with the active colors.

After CRT_setColors(mark), update the selected item and settings->colorScheme from CRT_colorScheme. Alternatively, disable this entry when CRT_hasDirectColors is false.

Comment thread CRT.c Outdated
Comment on lines +1505 to +1515
#if defined(NCURSES_EXT_FUNCS) && defined(NCURSES_EXT_COLORS)
if (CRT_hasDirectColors) {
int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;
init_extended_pair(ColorIndexGrayBlack, 0x949494, grayBlackBg);
} else
#endif
{
short int grayBlackFg = COLORS > 8 ? 8 : 0;
short int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;
init_pair(ColorIndexGrayBlack, grayBlackFg, grayBlackBg);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the specified light-gray RGB value.

Line 1508 uses 0x949494. The PR objective requires 0xA0A0A0. This value controls the gray elements that this change fixes.

Proposed fix
-      init_extended_pair(ColorIndexGrayBlack, 0x949494, grayBlackBg);
+      init_extended_pair(ColorIndexGrayBlack, 0xA0A0A0, grayBlackBg);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#if defined(NCURSES_EXT_FUNCS) && defined(NCURSES_EXT_COLORS)
if (CRT_hasDirectColors) {
int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;
init_extended_pair(ColorIndexGrayBlack, 0x949494, grayBlackBg);
} else
#endif
{
short int grayBlackFg = COLORS > 8 ? 8 : 0;
short int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;
init_pair(ColorIndexGrayBlack, grayBlackFg, grayBlackBg);
}
#if defined(NCURSES_EXT_FUNCS) && defined(NCURSES_EXT_COLORS)
if (CRT_hasDirectColors) {
int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;
init_extended_pair(ColorIndexGrayBlack, 0xA0A0A0, grayBlackBg);
} else
#endif
{
short int grayBlackFg = COLORS > 8 ? 8 : 0;
short int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;
init_pair(ColorIndexGrayBlack, grayBlackFg, grayBlackBg);
}

@rahawii
rahawii force-pushed the fix-direct-gray-2078 branch from 2c42504 to 8719305 Compare August 19, 2026 17:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 06318ec2-9756-46a5-9d56-e2d546a938f1

📥 Commits

Reviewing files that changed from the base of the PR and between 2c42504 and 8719305.

📒 Files selected for processing (1)
  • CRT.c

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.

Comment thread CRT.c Outdated
Comment thread CRT.c Outdated
Comment thread CRT.c
@rahawii
rahawii force-pushed the fix-direct-gray-2078 branch from 8719305 to 9123981 Compare August 19, 2026 19:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f0b96d63-4dad-4f4e-8ced-666155a38887

📥 Commits

Reviewing files that changed from the base of the PR and between 8719305 and 9123981.

📒 Files selected for processing (2)
  • CRT.c
  • configure.ac

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread CRT.c Outdated
@ravi-arnan

Copy link
Copy Markdown
Contributor

Tested this on the same harness I offered in #2078: a locally compiled xterm-direct entry carrying RGB and colors#0x1000000, ncurses 6.4, Linux, counting the emitted SGR sequences rather than reading the screen. It does what it says, and I could not break it. Comment rather than approval, I am not a maintainer.

The fix works, on every affected scheme

Same htoprc, same 40x160 screen, only color_scheme and TERM differ. Counting ESC[38:2::0:0:8m (the broken gray) against ESC[38:2::148:148:148m (the new one):

scheme 6f33ddd5 broken 0c649cf8 broken 0c649cf8 new gray
Default 78 0 71
Light Terminal 83 0 79
Black Night 24 0 10
Nord 86 0 82
Monochrome, Black on White, Midnight, Broken Gray 0 0 0

Totals move run to run because the process list does, so read the zeros and not the differences. The 256-colour path is untouched: xterm-256color still emits ESC[90m and zero truecolor sequences on both builds.

Four things I checked because they were the ways this could go wrong

The bold attribute survives. ColorPairGrayBlack is used as A_BOLD | ColorPairGrayBlack in these schemes, and an extended pair could have dropped it. It does not: every gray run is still preceded by ESC[0;1m, byte for byte the same as main on xterm-256color.

main   256color   <ESC>(B<ESC>[0;1m<ESC>[90m
main   direct     <ESC>(B<ESC>[0;1m<ESC>[38:2::0:0:8m
2079   direct     <ESC>(B<ESC>[0;1m<ESC>[38:2::148:148:148m

The default background survives -1. init_extended_pair taking -1 for the default colour is the other thing that could have quietly regressed, into an opaque black background. No 48:2: sequence is emitted anywhere in the patched captures, on any scheme, so the background is still the terminal's.

The scope is complete, not partial. Index 8 really was the only palette value htop ever asks for above 7: the pair loop at CRT.c:1350 runs i < 8 and j < 8, and ColorIndexWhiteDefault uses White. Direct-colour setaf routes anything below 8 through its legacy %{8}%< branch, so those are unaffected by construction. The measurement agrees: the patched Default capture contains exactly one distinct truecolor triple and it is yours.

All four direct variants, not just the one I built for. Using ncurses 6.4's own terminfo database:

TERM new gray broken
xterm-direct 71 0
xterm-direct2 105 0
xterm-direct16 87 0
xterm-direct256 82 0

Worth knowing if you reproduce this: xterm-direct2 emits 38:2:148:148:148 with two colons where xterm-direct emits 38:2::148:148:148 with three, an empty colourspace field. A grep written for one silently reports zero on the other, which is exactly what my first pass did.

Two things to raise, neither of them blocking

COLORS >= 16777216 is fine, and I checked rather than assumed. I scanned all 2817 entries in ncurses 6.4's terminfo database for a disagreement between RGB being set and colors reaching 0x1000000, in both directions. There are none, so the two predicates are interchangeable in practice.

The coordination point is #2072, not correctness. That PR adds CRT_hasDirectColors = (tigetflag("RGB") == 1) guarded on NCURSES_EXT_FUNCS and NCURSES_EXT_COLORS, while this one adds an independent COLORS >= 16777216 test plus a new AC_CHECK_FUNCS([init_extended_pair]). Whichever of the two lands second probably wants to collapse to one predicate and one capability check. On the capability half I would lean toward the macro test #2072 uses: on this ncurses the declaration of init_extended_pair sits inside the curses.h block that defines NCURSES_EXT_FUNCS (line 925 here), so that macro tracks whether the prototype exists, whereas AC_CHECK_FUNCS link-tests the symbol using its own decoy prototype. They agree here, and HAVE_INIT_EXTENDED_PAIR is detected correctly with a clean build under htop's full warning set.

0x949494 is theme-blind where SGR 90 was not. This is the one I would want an opinion on. Before the patch the gray was the terminal's own bright black, which the user themes; after it, on a direct terminal it is a fixed shade. On a dark background that is a clear improvement. Light Terminal is the case that gives me pause, since it exists for light backgrounds:

foreground on white on black
0x949494 3.03:1 6.92:1
0x808080, xterm's usual index 8 3.95:1 5.32:1

3.03:1 is below the 4.5:1 WCAG AA threshold for text. I want to be clear that this is arithmetic and not observation: I have no light-background direct-colour terminal here and did not see it rendered. If the value ends up scheme-dependent, or simply darker for Light Terminal and Black on White, the measurement above is the reason. If it is deliberate, a one-line comment naming the value would stop the next reader asking.

Everything else looks right to me, and thank you for turning this around so quickly after #2078.

@rahawii
rahawii force-pushed the fix-direct-gray-2078 branch from 0c649cf to 8cf4e50 Compare August 20, 2026 12:04
@ravi-arnan

Copy link
Copy Markdown
Contributor

Re-tested 8cf4e505 on the same harness. The contrast point is addressed, and I measured it rather than taking the constant on trust.

Per scheme, TERM=xterm-direct

scheme 0x5F5F5F 0x949494 RGB(0,0,8) other triples
Default 0 89 0 none
Light Terminal 96 0 0 none
Midnight 0 0 0 none
Black Night 0 16 0 none
Nord 0 85 0 none
Monochrome, Black on White, Broken Gray 0 0 0 none

So exactly one scheme moved, and it is the one that needed to. xterm-256color is still untouched on this head: ESC[90m and zero truecolor sequences on Default, Light Terminal and Nord. Clean build under htop's full warning set.

The counts above are what reaches the screen with my meter and column layout, not the number of elements in each scheme, which is why Midnight shows 0 despite having 2. Reading the scheme tables gives 9 / 0 / 0 / 8 / 2 / 5 / 0 / 18 references to ColorPairGrayBlack, matching the table in #2078 exactly.

The contrast, now

foreground on white on black
0x5F5F5F, light schemes 6.39:1 3.29:1
0x949494, everything else 3.03:1 6.92:1

Light Terminal goes from 3.03:1 to 6.39:1 against white, which clears AA with room. That was the ask and it is answered.

Worth saying out loud, since it is the other half of the same trade: a user running Light Terminal on a dark terminal now gets 3.29:1 where this morning's head gave them 6.92:1. I do not think that is worth changing. The scheme name is the only statement about the background that htop has, -1 leaves the actual background to the terminal, and someone picking Light Terminal on a dark terminal has already made a stranger choice than this. Recording the number so nobody discovers it as a surprise later.

Two small things

The COLORSCHEME_BLACKONWHITE arm is unreachable. That scheme's table has zero references to ColorPairGrayBlack, and it emitted zero sequences of either shade at runtime. So the arm costs nothing and does nothing. Fine as future-proofing if that is the intent, in which case a short comment would stop the next reader testing it the way I just did; otherwise it can go and the condition reads shorter.

Default on a light terminal is the case the heuristic still does not cover. It keeps 0x949494 at 3.03:1 against white. I would leave it: the answer for that user is to pick Light Terminal, which is what the scheme is for. Noting it only so the boundary of the fix is written down.

Nothing here blocks anything from my side, and the answer on the maintainer's actual question, whether to converge with #2072 on one direct-colour predicate and one capability check, has not changed.

@rahawii
rahawii force-pushed the fix-direct-gray-2078 branch from 8cf4e50 to a471831 Compare August 20, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid 🗑️ This doesn't seem right

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gray elements render as RGB(0,0,8) on direct-color terminals

2 participants