【问题标题】:Get console user input as typed, char by char获取控制台用户输入,按字符逐个字符输入
【发布时间】:2016-10-01 09:54:24
【问题描述】:

我在 Elixir 中有一个控制台应用程序。我需要在按键的基础上解释用户的输入。例如,我需要将“q”视为结束会话的命令,而无需用户显式按 也就是“回车”。

IO.getn/2 令人惊讶地等待 被按下,缓冲输入(我几乎可以肯定,这种缓冲是由控制台本身完成的,但 man stty 不提供任何帮助/标志关闭缓冲。)

Mix.Utils use the infinite loop 隐藏用户输入(基本上每 1 毫秒向控制台发送退格控制序列)IEx 代码包装对标准 erlang 的 io 的调用,它提供了在 Tab(用于自动完成)

我的猜测是我必须使用Port,将其附加到:stdin 并生成一个进程来监听输入。不幸的是,我坚持尝试实现后者,因为我需要附加到当前运行的控制台,而不是为其他进程创建新端口(因为它是 perfectly described here。)

我是否遗漏了一些关于如何将Port 附加到当前进程':stdin(顺便说一句在Port.list/0 中列出)的明显内容,或者我是否应该构建整个 3 管道架构以将输入的内容重定向到 :stdin 并将我的程序想要的 puts 重定向到 :stdout

【问题讨论】:

  • eralng IO 系统与其他语言非常不同,完全自定义。这使它以奇怪的方式表现。我已经多次看到这样的问题出现了,但不幸的是,我从未见过一个好的答案。据我所知,像你问的那样是不可能的,但我可能错了,所以我不会把它作为答案发布。
  • @michalmuskala 我的问题的最后一段实际上包含了关于如何完成的现成答案。唯一的问题是它看起来过于复杂,我怀疑没有人已经解决了这个问题。

标签: console port command-line-interface elixir


【解决方案1】:

您的程序无法获取按键,因为在 Linux 上,终端默认位于 cooked mode,它会缓冲所有按键直到按下 Return 键。

您需要将终端切换到原始模式,该模式会在按键发生后立即将其发送到应用程序。没有跨平台可以做到这一点。

对于类 unix 系统,有 ncurses,它有一个长生不老药绑定,您应该检查一下:https://github.com/jfreeze/ex_ncurses。它甚至有一个例子来做你想做的事。

【讨论】:

  • 是的,谢谢,这听起来是最合适的方法。不过,我会等待 José 说些什么 [希望如此]。
  • 我之所以选择这个是因为使用 ncurses 给了我或多或少的开箱即用的强大解决方案(但也有一些缺点。) JIC:我需要它在 @987654323 中进行调查@ package,rake release mix/hex 的替代品。
【解决方案2】:

我能做的最简单的事情是基于this github repo。所以你需要以下内容:

reader.c

#include "erl_driver.h"
#include <stdio.h>

typedef struct {
  ErlDrvPort drv_port;
} state;

static ErlDrvData start(ErlDrvPort port, char *command) {
  state *st = (state *)driver_alloc(sizeof(state));
  st->drv_port = port;
  set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
  driver_select(st->drv_port, (ErlDrvEvent)(size_t)fileno(stdin), DO_READ, 1);
  return (ErlDrvData)st;
}

static void stop(ErlDrvData drvstate) {
  state *st = (state *)drvstate;
  driver_select(st->drv_port, (ErlDrvEvent)(size_t)fileno(stdin), DO_READ, 0);
  driver_free(drvstate);
}

static void do_getch(ErlDrvData drvstate, ErlDrvEvent event) {
  state *st = (state *)drvstate;
  char* buf = malloc(1);
  buf[0] = getchar();
  driver_output(st->drv_port, buf, 1);
}

ErlDrvEntry driver_entry = {
  NULL,
  start,
  stop,
  NULL,
  do_getch,
  NULL,
  "reader",
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  ERL_DRV_EXTENDED_MARKER,
  ERL_DRV_EXTENDED_MAJOR_VERSION,
  ERL_DRV_EXTENDED_MINOR_VERSION
};

DRIVER_INIT(reader) {
  return &driver_entry;
}

gcc -o reader.so -fpic -shared reader.c 编译它。 然后你需要在reader.erl

-module(reader).
-behaviour(gen_server).
-export([start/0, init/1, terminate/2, read/0, handle_cast/2, code_change/3, handle_call/3, handle_info/2, getch/0]).
-record(state, {port, caller}).

start() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, no_args, []).

getch() ->
    gen_server:call(?MODULE, getch, infinity).

handle_call(getch, From, #state{caller = undefined} = State) ->
    {noreply, State#state{caller = From}};
handle_call(getch, _From, State) ->
    {reply, -1, State}.

handle_info({_Port, {data, _Binary}}, #state{ caller = undefined } = State) ->
    {noreply, State};
handle_info({_Port, {data, Binary}}, State) ->
    gen_server:reply(State#state.caller, binary_to_list(Binary)),
    {noreply, State#state{ caller = undefined }}.

init(no_args) ->
    case erl_ddll:load(".","reader") of
    ok -> 
        Port = erlang:open_port({spawn, "reader"}, [binary]),
        {ok, #state{port = Port}};
    {error, ErrorCode} -> 
        exit({driver_error, erl_ddll:format_error(ErrorCode)})
    end.


handle_cast(stop, State) ->    
    {stop, normal, State};
handle_cast(_, State) ->    
    {noreply, State}.

code_change(_, State, _) ->
    {noreply, State}.

terminate(_Reason, State) ->
    erlang:port_close(State#state.port),
    erl_ddll:unload("reader").

read() ->
    C = getch(),
    case C of
    "q" ->
        gen_server:cast(?MODULE, stop);
    _ ->
        io:fwrite("Input received~n",[]),
        read()
    end.

erlc reader.erl编译它。

然后在 iex :reader.start(); :reader.read() 中,它会发出一个警告,指出 stdin 已被劫持,并且每次按键您都会收到 收到的输入。唯一的问题是,当您按下 q 时,服务器会终止,但无法访问 stdin

【讨论】:

  • 谢谢,这看起来很方便。因为我需要更多类似ncurses 的功能,所以我将依赖于整个实现而不是它的剖析部分,但这绝对是朝着正确方向迈出的一步。现在我要决定我要选择ncurses 的哪个实现:你提到的那个,或者@Dirbaio 提出的ex_ncurses
  • 好吧,我终于使用了ncurses,因为我无法克服警告,而且就我而言,这是不可接受的。我相信,有办法解决它,我不会放弃尝试,但作为开箱即用的解决方案,ncurses 满足了我当前的所有需求。无论如何,谢谢你,当我最终决定完全与控制台作战时,这个答案帮助我理解了我要实现的目标。
猜你喜欢
  • 1970-01-01
  • 2013-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-21
  • 1970-01-01
  • 2017-01-21
相关资源
最近更新 更多