Conversation
These wrap wattr_set() and wattr_get() respectively, exposing the full extended color pair number (> 255) that attrset/color_set cannot represent. attr_set(attrs, pair) sets both attributes and color pair; attr_get returns [attrs, pair] as a 2-element Array. Both are guarded by HAVE_WATTR_SET / HAVE_WATTR_GET and fall back to rb_f_notimplement when unavailable. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR exposes the ncurses extended attribute API to Ruby by wrapping wattr_set() and wattr_get() as Window#attr_set and Window#attr_get. Unlike the existing attrset/color_set, these methods operate on a separate color pair integer (rather than embedding the pair inside a chtype), allowing pair numbers greater than 255. Both methods are guarded with HAVE_WATTR_SET/HAVE_WATTR_GET and fall back to rb_f_notimplement when unavailable.
Changes:
- Adds
window_attr_setC function implementingWindow#attr_set(attrs, pair)viawattr_set() - Adds
window_attr_getC function implementingWindow#attr_getviawattr_get(), returning[attrs, pair] - Registers both methods on
cWindowviarb_define_method
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ext/curses/curses.c
Outdated
| struct windata *winp; | ||
|
|
||
| GetWINDOW(obj, winp); | ||
| return (wattr_set(winp->window, NUM2ULONG(attrs), NUM2INT(pair), NULL) == OK) ? Qtrue : Qfalse; |
There was a problem hiding this comment.
The NUM2ULONG macro converts the Ruby Integer to unsigned long, but wattr_set's second parameter expects attr_t, which in ncurses is unsigned int (32-bit). On 64-bit platforms, unsigned long is 64-bit, so this causes an implicit narrowing conversion warning and potentially incorrect behavior if the value is wider than 32 bits. The correct conversion macro here should be NUM2UINT to match attr_t (which is an unsigned int-sized type). Compare to how the existing window_color_set at line 2811 simply passes attrs as-is after reading it via wattr_get.
| return (wattr_set(winp->window, NUM2ULONG(attrs), NUM2INT(pair), NULL) == OK) ? Qtrue : Qfalse; | |
| return (wattr_set(winp->window, NUM2UINT(attrs), NUM2INT(pair), NULL) == OK) ? Qtrue : Qfalse; |
ext/curses/curses.c
Outdated
| GetWINDOW(obj, winp); | ||
| if (wattr_get(winp->window, &attrs, &pair, NULL) == ERR) | ||
| return Qnil; | ||
| return rb_ary_new3(2, ULONG2NUM(attrs), INT2FIX(pair)); |
There was a problem hiding this comment.
ULONG2NUM(attrs) converts attrs (of type attr_t, which is unsigned int) to a Ruby Integer via unsigned long. This implicit widening from unsigned int to unsigned long is harmless on most platforms, but it is semantically imprecise. UINT2NUM(attrs) is the correct macro for an unsigned int-sized type. Additionally, INT2FIX(pair) is used for the pair value, but INT2FIX is only valid for values in the Fixnum range. Since NCURSES_PAIRS_T is an int supporting extended color pairs (which can be a large positive number), INT2NUM(pair) is the safer choice here. Notably, INT2FIX is used rather than INT2NUM throughout other parts of the codebase (e.g., line 1459), but the intent of attr_get is specifically to support large extended pair numbers, so losing that via an unsafe INT2FIX would be a bug if the pair number is large.
| return rb_ary_new3(2, ULONG2NUM(attrs), INT2FIX(pair)); | |
| return rb_ary_new3(2, UINT2NUM(attrs), INT2NUM(pair)); |
- attr_set: NUM2ULONG -> NUM2UINT to match attr_t (unsigned int) - attr_get: ULONG2NUM -> UINT2NUM for attr_t; INT2FIX -> INT2NUM for pair number to safely handle extended pair values on all platforms Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These wrap wattr_set() and wattr_get() respectively, exposing the full extended color pair number (> 255) that attrset/color_set cannot represent. attr_set(attrs, pair) sets both attributes and color pair; attr_get returns [attrs, pair] as a 2-element Array.
Both are guarded by HAVE_WATTR_SET / HAVE_WATTR_GET and fall back to rb_f_notimplement when unavailable.