-module(input_ffi). -behaviour(gen_server). -export([start_link/0, poll/1, push/1, read/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, restart_or_start_input_loop/0]). -record(state, {queue, waiter = none, input_loop_pid = none}). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). init([]) -> gen_event:add_handler(erl_signal_server, signal_handler, self()), {ok, #state{queue = queue:new(), input_loop_pid = spawn_link(etch@erlang@input, input_loop, [tty_state:is_raw_mode()])}}. poll(TimeoutMs) when is_integer(TimeoutMs), TimeoutMs >= 0 -> case whereis(?MODULE) of undefined -> start_link(), gen_server:call(?MODULE, {poll, TimeoutMs}, TimeoutMs + 50); _Pid -> gen_server:call(?MODULE, {poll, TimeoutMs}, TimeoutMs + 50) end. read() -> case whereis(?MODULE) of undefined -> start_link(), gen_server:call(?MODULE, read, infinity); _Pid -> gen_server:call(?MODULE, read, infinity) end. restart_or_start_input_loop() -> case whereis(?MODULE) of undefined -> start_link(), gen_server:call(?MODULE, restart_or_start_input_loop, infinity); _Pid -> gen_server:call(?MODULE, restart_or_start_input_loop, infinity) end. push(Event) -> gen_server:cast(?MODULE, {push, Event}). handle_call({poll, _Timeout}, _From, #state{queue = Q} = S) -> case queue:out(Q) of {empty, _} -> {reply, none, S}; {{value, Ev}, Q2} -> {reply, {some, Ev}, S#state{queue = Q2}} end; handle_call(read, From, #state{queue = Q} = S) -> case queue:out(Q) of {empty, _} -> {noreply, S#state{waiter = From}}; {{value, Ev}, Q2} -> {reply, {some, Ev}, S#state{queue = Q2}} end; handle_call(restart_or_start_input_loop, _From, #state{} = S) -> Is_raw = tty_state:is_raw_mode(), case S of #state{input_loop_pid = none} -> {reply, ok, S#state{input_loop_pid = spawn_link(etch@erlang@input, input_loop, [Is_raw])}}; #state{input_loop_pid = Pid} -> unlink(Pid), exit(Pid, kill), {reply, ok, S#state{input_loop_pid = spawn_link(etch@erlang@input, input_loop, [Is_raw])}} end; handle_call(_Other, _From, State) -> {reply, ok, State}. handle_cast({push, Ev}, #state{queue = Q, waiter = none} = S) -> {noreply, S#state{queue = queue:in(Ev, Q)}}; handle_cast({push, Ev}, #state{waiter = Waiter} = S) -> gen_server:reply(Waiter, {some, Ev}), {noreply, S#state{waiter = none}}; handle_cast(_Other, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. handle_info(_Info, S) -> {noreply, S}.