Get console user input as typed, char by char

前端 未结 2 1583
粉色の甜心
粉色の甜心 2020-12-20 12:30

I have a console application in Elixir. I need to interpret user’s input on by keypress basis. For instance, I need to treat “q” as a command to end the session, without use

2条回答
  •  别那么骄傲
    2020-12-20 13:12

    The simplest thing I could cook up is based on this github repo. So you need the following:

    reader.c

    #include "erl_driver.h"
    #include 
    
    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;
    }
    

    compile it with gcc -o reader.so -fpic -shared reader.c. Then you'll need in 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.
    

    Compile it with erlc reader.erl.

    Then in iex :reader.start(); :reader.read() it issues a warning that stdin has been hijacked, and for every keypress you get Input received. The only problem is that when you press q the server terminates, but the stdin is not accessible.

提交回复
热议问题