Skip to content

Commit

Permalink
Added async-input.
Browse files Browse the repository at this point in the history
The input is now done by ncurses itself. The driver starts a thread that will do getch() continuously and send a message back to Erlang when it has a character. meanwhile if a process asks for a char (cecho:getch()) it will block by first sending a message to the cecho_srv process and "register" itself as waiting and then as soon as a key input is read from the driver it will be sent to it.

If a message is received from the port and no process is waiting for the character then the message will be discarded.
If a process tries to do cecho:getch() at the same time as another one is already blocking the process that called cecho:getch() last will be killed.

If special characters need to be captured then cecho:keypad/2 must be used to enable keypad mode.

NOTE: erl must be started with +A <N> where N is a number (probably above 40, try e.g. 50 or above) AND the flag -noinput must be given to stop the erlang shell from reading input (and thus competing) with cecho. To test the new feature run cecho_example:input() as follows:

$> pwd
/home/mazen/dev/repos/cecho
$> erl -pa ../cecho/ebin/ -noinput -eval 'cecho_example:input(),halt().' +A 50
$>

Use 'q' to quit.
  • Loading branch information
mazenharake committed Mar 25, 2010
1 parent 4769266 commit 7f6fd67
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 10 deletions.
20 changes: 19 additions & 1 deletion c_src/cecho.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ void integer(ei_x_buff *eixb, int integer);
void string(ei_x_buff *eixb, const char *str);
void encode_ok_reply(state *st, int code);
int findfreewindowslot(state *st);
void loop_getch(void *arg);

void do_endwin(state *st);
void do_initscr(state *st);
Expand Down Expand Up @@ -116,6 +117,11 @@ static void stop(ErlDrvData drvstate) {
driver_free(drvstate);
}

static void init_getch_loop(ErlDrvData drvstate, char *buf, int buflen) {
state *st = (state *)drvstate;
driver_async(st->drv_port, NULL, loop_getch, (void *)st, NULL);
}

static int ctrl(ErlDrvData drvstate, unsigned int command, char *args,
int argslen, char **rbuf, int rbuflen) {
state *st = (state *)drvstate;
Expand Down Expand Up @@ -549,14 +555,26 @@ int findfreewindowslot(state *st) {
return -1;
}

void loop_getch(void *arg) {
state *st = (state *)arg;
ei_x_buff eixb;
int keycode;
while(1) {
ei_x_new_with_version(&eixb);
keycode = getch();
integer(&eixb, keycode);
driver_output(st->drv_port, eixb.buff, eixb.index);
}
}

// =============================================================================
// Erlang driver_entry Specification
// ===========================================================================
ErlDrvEntry driver_entry = {
NULL,
start,
stop,
NULL,
init_getch_loop,
NULL,
NULL,
"cecho",
Expand Down
5 changes: 4 additions & 1 deletion src/cecho.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
scrollok/2, mvaddch/3, mvaddstr/3, newwin/4, delwin/1, wmove/3,
waddstr/2, waddch/2, mvwaddstr/4, mvwaddch/4, wrefresh/1, hline/2,
whline/3, vline/2, wvline/3, border/8, wborder/9, box/3, getyx/1,
getmaxyx/1, attron/2, attroff/2, keypad/2]).
getmaxyx/1, attron/2, attroff/2, keypad/2, getch/0]).

%% =============================================================================
%% Application API
Expand Down Expand Up @@ -191,6 +191,9 @@ box(Window, Vert, Horz) when is_integer(Window) andalso is_integer(Vert) andalso
keypad(Window, BFlag) when is_integer(Window) andalso is_boolean(BFlag) ->
call(?KEYPAD, {Window, BFlag}).

getch() ->
cecho_srv:getch().

%% =============================================================================
%% Behaviour Callbacks
%% =============================================================================
Expand Down
3 changes: 2 additions & 1 deletion src/cecho_example.erl
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,14 @@ input() ->
ok = cecho:cbreak(),
ok = cecho:noecho(),
ok = cecho:curs_set(?ceCURS_INVISIBLE),
ok = cecho:keypad(?ceSTDSCR, true),
spawn_link(?MODULE, input_counter, [0]),
cecho:mvaddstr(9, 10, "Enter: "),
cecho:refresh(),
input_reader().

input_reader() ->
[P] = io:get_chars('',1),
P = cecho:getch(),
case P of
113 ->
application:stop(cecho);
Expand Down
30 changes: 23 additions & 7 deletions src/cecho_srv.erl
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@
code_change/3]).

%% Module API
-export([start_link/0, call/2 ]).
-export([start_link/0, call/2, getch/0]).

%% Records
-record(state, { port }).
-record(state, { port, getpid }).

%% =============================================================================
%% Module API
Expand All @@ -49,17 +49,25 @@ start_link() ->
call(Cmd, Args) ->
gen_server:call(?MODULE, {call, Cmd, Args}, infinity).

getch() ->
?MODULE ! {getch, self()},
receive
{ch, Chr} ->
Chr
end.

%% =============================================================================
%% Behaviour Callbacks
%% =============================================================================
init(no_args) ->
process_flag(trap_exit, true),
case erl_ddll:load(code:priv_dir(cecho)++"/lib","cecho") of
ok ->
Port = erlang:open_port({spawn, "cecho"}, []),
Port = erlang:open_port({spawn, "cecho"}, [binary]),
ok = do_call(Port, ?INITSCR),
ok = do_call(Port, ?ERASE),
ok = do_call(Port, ?REFRESH),
erlang:port_command(Port, <<>>),
{ok, #state{ port = Port }};
{error, ErrorCode} ->
exit({driver_error, erl_ddll:format_error(ErrorCode)})
Expand All @@ -75,12 +83,20 @@ terminate(_Reason, State) ->
erlang:port_close(State#state.port),
erl_ddll:unload("cecho").

%% @hidden
handle_cast(_, State) ->
{noreply, State}.
handle_info({getch, From}, #state{ getpid = undefined } = State) ->
{noreply, State#state{ getpid = From }};
handle_info({getch, From}, State) ->
exit(From, kill),
{noreply, State};
handle_info({_Port, {data, _Binary}}, #state{ getpid = undefined } = State) ->
{noreply, State};
handle_info({_Port, {data, Binary}}, State) ->
Ch = binary_to_term(Binary),
State#state.getpid ! {ch, Ch},
{noreply, State#state{ getpid = undefined }}.

%% @hidden
handle_info(_, State) ->
handle_cast(_, State) ->
{noreply, State}.

%% @hidden
Expand Down

0 comments on commit 7f6fd67

Please sign in to comment.