Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions ext/io/console/console.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ getattr(int fd, conmode *t)
static ID id_getc, id_close;
static ID id_gets, id_flush, id_chomp_bang;

#ifndef HAVE_RB_INTERNED_STR_CSTR
# define rb_str_to_interned_str(str) rb_str_freeze(str)
# define rb_interned_str_cstr(str) rb_str_freeze(rb_usascii_str_new_cstr(str))
#endif

#if defined HAVE_RUBY_FIBER_SCHEDULER_H
# include "ruby/fiber/scheduler.h"
#elif defined HAVE_RB_SCHEDULER_TIMEOUT
Expand Down Expand Up @@ -1818,6 +1823,61 @@ io_getpass(int argc, VALUE *argv, VALUE io)
return str_chomp(str);
}

#if defined(_WIN32) || defined(HAVE_TTYNAME_R) || defined(HAVE_TTYNAME)
/*
* call-seq:
* io.ttyname -> string or nil
*
* Returns name of associated terminal (tty) if +io+ is not a tty.
* Returns +nil+ otherwise.
*/
static VALUE
console_ttyname(VALUE io)
{
int fd = rb_io_descriptor(io);
if (!isatty(fd)) return Qnil;
# if defined _WIN32
return rb_usascii_str_new_lit("con");
# elif defined HAVE_TTYNAME_R
{
char termname[1024], *tn = termname;
size_t size = sizeof(termname);
int e;
if (ttyname_r(fd, tn, size) == 0)
return rb_interned_str_cstr(tn);
if ((e = errno) == ERANGE) {
VALUE s = rb_str_new(0, size);
while (1) {
tn = RSTRING_PTR(s);
size = rb_str_capacity(s);
if (ttyname_r(fd, tn, size) == 0) {
return rb_str_to_interned_str(rb_str_resize(s, strlen(tn)));
}
if ((e = errno) != ERANGE) break;
if ((size *= 2) >= INT_MAX/2) break;
rb_str_resize(s, size);
}
}
rb_syserr_fail_str(e, rb_sprintf("ttyname_r(%d)", fd));
UNREACHABLE_RETURN(Qnil);
}
# elif defined HAVE_TTYNAME
{
const char *tn = ttyname(fd);
if (!tn) {
int e = errno;
rb_syserr_fail_str(e, rb_sprintf("ttyname(%d)", fd));
}
return rb_interned_str_cstr(tn);
}
# else
# error No ttyname function
# endif
}
#else
# define console_ttyname rb_f_notimplement
#endif

/*
* IO console methods
*/
Expand Down Expand Up @@ -1885,6 +1945,7 @@ InitVM_console(void)
rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1);
rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0);
rb_define_method(rb_cIO, "getpass", console_getpass, -1);
rb_define_method(rb_cIO, "ttyname", console_ttyname, 0);
rb_define_singleton_method(rb_cIO, "console", console_dev, -1);
{
/* :stopdoc: */
Expand Down
2 changes: 2 additions & 0 deletions ext/io/console/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
have_func("rb_syserr_new_str(0, Qnil)") or
abort

have_func("rb_interned_str_cstr")
have_func("rb_io_path")
have_func("rb_io_descriptor")
have_func("rb_io_get_write_io")
Expand Down Expand Up @@ -51,6 +52,7 @@
elsif have_func("rb_scheduler_timeout") # 3.0
have_func("rb_io_wait")
end
have_func("ttyname_r") or have_func("ttyname")
create_makefile("io/console") {|conf|
conf << "\n""VK_HEADER = #{vk_header}\n"
}
Expand Down
25 changes: 22 additions & 3 deletions test/io/console/test_io_console.rb
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,11 @@ def test_console_kw
def test_sync
assert_equal(["true"], run_pty("p IO.console.sync"))
end

def test_ttyname
return unless IO.method_defined?(:ttyname)
assert_equal(["true"], run_pty("p STDIN.ttyname == STDOUT.ttyname"))
end
end

private
Expand Down Expand Up @@ -531,6 +536,13 @@ def test_sync
def test_getch_timeout
assert_nil(IO.console.getch(intr: true, time: 0.1, min: 0))
end

def test_ttyname
return unless IO.method_defined?(:ttyname)
ttyname = IO.console.ttyname
assert_not_nil(ttyname)
File.open(ttyname) {|f| assert_predicate(f, :tty?)}
end
end
end

Expand All @@ -546,7 +558,7 @@ def test_getch_timeout
if noctty
require 'tempfile'
NOCTTY = noctty
def test_noctty
def run_noctty(src)
t = Tempfile.new("noctty_out")
t.close
t2 = Tempfile.new("noctty_run")
Expand All @@ -557,7 +569,7 @@ def test_noctty
'-e', 'STDOUT.reopen(f)',
'-e', 'STDERR.reopen(f)',
'-e', 'require "io/console"',
'-e', 'f.puts IO.console.inspect',
'-e', "f.puts (#{src}).inspect",
'-e', 'f.flush',
'-e', 'File.unlink(ARGV[1])',
'-e', '}',
Expand All @@ -568,11 +580,18 @@ def test_noctty
sleep 0.1
end
t.open
assert_equal("nil", t.gets(nil).chomp)
t.gets.lines(chomp: true)
ensure
t.close! if t and !t.closed?
t2.close!
end

def test_noctty
assert_equal(["nil"], run_noctty("IO.console"))
if IO.method_defined?(:ttyname)
assert_equal(["nil"], run_noctty("STDIN.ttyname rescue $!"))
end
end
end
end

Expand Down