Skip to content

Commit 9db579f

Browse files
committed
Add cursor visibility controls
Add cross-platform cursor visibility operations so console users can hide the cursor while redrawing without platform-specific code.
1 parent cea1164 commit 9db579f

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

ext/io/console/console.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,54 @@ console_cursor_pos(VALUE io)
14101410
#endif
14111411
}
14121412

1413+
static VALUE
1414+
console_cursor_visibility(VALUE io, int visible)
1415+
{
1416+
#ifdef _WIN32
1417+
HANDLE h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io));
1418+
CONSOLE_CURSOR_INFO info;
1419+
1420+
if (!GetConsoleCursorInfo(h, &info)) {
1421+
rb_syserr_fail(LAST_ERROR, 0);
1422+
}
1423+
info.bVisible = visible;
1424+
if (!SetConsoleCursorInfo(h, &info)) {
1425+
rb_syserr_fail(LAST_ERROR, 0);
1426+
}
1427+
#else
1428+
rb_io_write(io, rb_str_new_cstr(visible ? CSI "?25h" : CSI "?25l"));
1429+
#endif
1430+
return io;
1431+
}
1432+
1433+
/*
1434+
* call-seq:
1435+
* io.hide_cursor -> io
1436+
*
1437+
* Hides the cursor.
1438+
*
1439+
* You must require 'io/console' to use this method.
1440+
*/
1441+
static VALUE
1442+
console_hide_cursor(VALUE io)
1443+
{
1444+
return console_cursor_visibility(io, 0);
1445+
}
1446+
1447+
/*
1448+
* call-seq:
1449+
* io.show_cursor -> io
1450+
*
1451+
* Shows the cursor.
1452+
*
1453+
* You must require 'io/console' to use this method.
1454+
*/
1455+
static VALUE
1456+
console_show_cursor(VALUE io)
1457+
{
1458+
return console_cursor_visibility(io, 1);
1459+
}
1460+
14131461
/*
14141462
* call-seq:
14151463
* io.goto(line, column) -> io
@@ -2201,6 +2249,8 @@ InitVM_console(void)
22012249
rb_define_method(rb_cIO, "goto", console_goto, 2);
22022250
rb_define_method(rb_cIO, "cursor", console_cursor_pos, 0);
22032251
rb_define_method(rb_cIO, "cursor=", console_cursor_set, 1);
2252+
rb_define_method(rb_cIO, "hide_cursor", console_hide_cursor, 0);
2253+
rb_define_method(rb_cIO, "show_cursor", console_show_cursor, 0);
22042254
rb_define_method(rb_cIO, "cursor_up", console_cursor_up, 1);
22052255
rb_define_method(rb_cIO, "cursor_down", console_cursor_down, 1);
22062256
rb_define_method(rb_cIO, "cursor_left", console_cursor_left, 1);

test/io/console/test_io_console.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,16 @@ def test_cursor_position
444444
end
445445
end
446446

447+
def test_cursor_visibility
448+
run_pty(<<~'RUBY') do |r, _, _|
449+
con = IO.console
450+
abort unless con.hide_cursor.equal?(con)
451+
abort unless con.show_cursor.equal?(con)
452+
RUBY
453+
assert_equal("\e[?25l\e[?25h", r.read(12))
454+
end
455+
end unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
456+
447457
def assert_ctrl(expect, cc, r, w)
448458
sleep 0.1
449459
w.print cc
@@ -700,6 +710,36 @@ def test_pressed_invalid
700710

701711
RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ and defined?(IO.console) and IO.console and \
702712
TestIO_Console.class_eval do
713+
def test_cursor_visibility
714+
require "fiddle/import"
715+
716+
kernel32 = Module.new do
717+
extend Fiddle::Importer
718+
dlload "kernel32.dll"
719+
extern "void *CreateFileW(void *, long, long, void *, long, long, void *)"
720+
extern "int CloseHandle(void *)"
721+
extern "int GetConsoleCursorInfo(void *, void *)"
722+
end
723+
File.open("CONOUT$", "r+") do |output|
724+
info = [0, 0].pack("L<L<")
725+
path = "CONOUT$\0".encode("UTF-16LE")
726+
handle = kernel32.CreateFileW(path, -0x40000000, 3, nil, 3, 0, nil)
727+
assert_not_equal(0, kernel32.GetConsoleCursorInfo(handle, info))
728+
size, visible = info.unpack("L<L<")
729+
begin
730+
assert_same(output, output.hide_cursor)
731+
assert_not_equal(0, kernel32.GetConsoleCursorInfo(handle, info))
732+
assert_equal([size, 0], info.unpack("L<L<"))
733+
assert_same(output, output.show_cursor)
734+
assert_not_equal(0, kernel32.GetConsoleCursorInfo(handle, info))
735+
assert_equal([size, 1], info.unpack("L<L<"))
736+
ensure
737+
visible == 0 ? output.hide_cursor : output.show_cursor
738+
kernel32.CloseHandle(handle)
739+
end
740+
end
741+
end
742+
703743
def test_console_input_events
704744
require "fiddle"
705745

0 commit comments

Comments
 (0)