2016-10-01 30 views
11

我在Elixir中有一個控制檯應用程序。我需要通過按鍵解釋用戶的輸入。舉例來說,我需要把「Q」作爲命令結束會話,無需用戶明確地按又名「回車」。獲取控制檯用戶輸入鍵入的字符char按鍵

IO.getn/2出奇等待被擠壓,緩衝的輸入(我幾乎可以肯定,這個緩衝由控制檯本身完成,但是man stty不提供任何幫助/標誌關閉緩衝。)

Mix.Utilsuse the infinite loop隱藏用戶輸入(基本上是發送退格鍵控制序列安慰每1ms,)IEx代碼將調用標準erlang的io,即提供了設置的回調上標籤唯一的能力(對於自動完成。)

我的猜測是我必須使用Port,將其連接到:stdin和產卵的過程聽輸入。不幸的是,我堅持試圖實現後者,因爲我需要附加到當前正在運行的控制檯,而不是創建一個新的端口到其他進程(因爲它是perfectly described here。)

我是否缺少明顯的東西我是一個Port附加到當前進程的:stdin(在Port.list/0 BTW上市)或者我應該已經建立了全3管道架構重定向什麼類型以:stdin並不管我的程序要puts:stdout

+0

該eralng IO系統是非常不同於其他語言和完全自定義。這使它表現出奇怪的方式。我看過很多次這樣的問題,但不幸的是,我從來沒有見過一個好的答案。據我所知,像你問的東西是不可能的,但我可能是錯的,所以我不會發布這個答案。 – michalmuskala

+0

@michalmuskala我的問題的最後一部分實際上包含了可以如何完成的隨時可用的答案。唯一的問題是它看起來過於複雜,我懷疑沒有人已經解決了這個問題。 – mudasobwa

回答

4

你的程序沒有得到密鑰,因爲在Linux上,終端默認爲cooked mode,它將緩存所有的按鍵,直到按下Return鍵。

您需要將終端切換到原始模式,該模式會在發生按鍵後立即嚮應用程序發送按鍵。沒有跨平臺來做到這一點。

對於類Unix系統,有ncurses,它有一個靈藥綁定,你應該檢查出來:https://github.com/jfreeze/ex_ncurses。它甚至有一個例子來做你想做的事。

+0

是的,謝謝,這聽起來像是最合適的方法。不過,我會等待José說[希望]。 – mudasobwa

+0

我選擇了這個,因爲使用'ncurses'給了我一個或多或少穩健的解決方案(雖然有一些缺點)。JIC:我需要它在['issuer'](https:/ /github.com/am-kantox/issuer)包,'rake release'替代mix/hex。 – mudasobwa

4

我可以做的最簡單的事情是基於this github回購。所以,你需要以下條件:

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不可訪問。

+0

謝謝,這看起來很方便。由於我需要更多類似ncurses的功能,因此我將依賴整個實現而不是解剖部分,但這絕對是朝着正確方向邁出的一步。現在我要決定我要選擇的'ncurses'的實現:你提到的那個,還是@Dirbaio提出的'ex_ncurses' – mudasobwa

+0

嗯,我終於使用了'ncurses',因爲我無法克服這個警告,在我的情況下,這是不可接受的。我相信,可能有辦法解決這個問題,我不會放棄嘗試,但作爲開箱即用的解決方案,'ncurses'滿足了我目前的所有需求。無論如何,謝謝你,這個答案幫助我理解了當我終於決定完全打開控制檯時我要實現的內容。 – mudasobwa