diff --git a/x04-noisy-echo/ansi.hrl b/x04-noisy-echo/ansi.hrl index bee7090..c834754 100644 --- a/x04-noisy-echo/ansi.hrl +++ b/x04-noisy-echo/ansi.hrl @@ -208,3 +208,21 @@ % 2. Control sequences reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html % 3. Historical background: https://invisible-island.net/xterm/modified-keys.html %-define(XTERM_ + +% readline keys +-define(KBD_ESC, [27]). +-define(KBD_C_a, [1]). +-define(KBD_C_b, [2]). +-define(KBD_C_d, [4]). +-define(KBD_C_e, [5]). +-define(KBD_C_f, [6]). +-define(KBD_C_g, [7]). +-define(KBD_C_k, [11]). +-define(KBD_C_l, [12]). +-define(KBD_C_n, [14]). +-define(KBD_C_p, [16]). + +% M-b -> [ESC, $b] +-define(KBD_M_b, [?KBD_ESC, $b]). +-define(KBD_M_d, [?KBD_ESC, $d]). +-define(KBD_M_f, [?KBD_ESC, $f]). diff --git a/x04-noisy-echo/kbd_debug b/x04-noisy-echo/kbd_debug new file mode 100755 index 0000000..b5bc32a --- /dev/null +++ b/x04-noisy-echo/kbd_debug @@ -0,0 +1,12 @@ +#!/usr/bin/env escript + +%% script for keycode mapping + +main([]) -> + ok = shell:start_interactive({noshell, raw}), + dum([]). + +dum(Q) -> + io:format("~tw\r\n", [Q]), + NewChar = io:get_chars("", 1), + dum(Q ++ NewChar). diff --git a/x04-noisy-echo/nechoc b/x04-noisy-echo/nechoc index d721f4f..f65cb18 100755 --- a/x04-noisy-echo/nechoc +++ b/x04-noisy-echo/nechoc @@ -1,18 +1,32 @@ #!/usr/bin/env escript +%% needs: +%% - line parsing +%% - keyboard input +%% - drawing to screen + -mode(compile). % john state -record(js, - {tsock = none :: gen_tcp:socket(), - nsock = none :: enoise:noise_socket()}). + {rcv = <<>> :: binary(), + kbdq = [] :: iolist(), + ibuf = [] :: string(), + tsock = none :: none | gen_tcp:socket(), + nsock = none :: none | enoise:noise_socket()}). -main([]) -> +main([]) -> main_ii(6969); +main([PortNum]) -> main_ii(list_to_integer(PortNum)). + +main_ii(PortNum) -> TcpOpts = [binary, {packet, 0}, {active, once}], - {ok, TSock} = gen_tcp:connect("localhost", 6969, TcpOpts), + {ok, TSock} = gen_tcp:connect("localhost", PortNum, TcpOpts), ok = inet:setopts(TSock, [{active, once}]), {ok, NSock, _HandshakeState} = enoise:connect(TSock, noise_options()), + % need keyboard process + Self = self(), + spawn_link(fun() -> kbd_loop(Self) end), j_loop(#js{tsock = TSock, nsock = NSock}). noise_options() -> @@ -22,15 +36,73 @@ noise_options() -> {e, KP}]. -j_loop(JS = #js{nsock = NSock}) -> +j_loop(JS = #js{rcv = Rcv, tsock = TSock, nsock = NSock}) -> + ok = inet:setopts(TSock, [{active, once}]), receive {noise, NSock, Bytes} -> - ok = io:format("< ~tp~n", [Bytes]), - j_loop(JS); + NewRcv = <>, + io:format("NewRcv = ~p~n", [NewRcv]), + NewJS = j_received(NewRcv, JS), + j_loop(NewJS); + {kbd, line, Line} -> + NewJS = j_kbdln(Line, JS), + j_loop(NewJS); + {tcp_closed, TSock} -> + io:format("tcp socket closed unexpectedly, exiting~n"), + exit(tcp_closed); Unknown -> - ok = io:format("~tp unknown message, killing self: ~tp~n", - [self(), Unknown]), - exit(normal) + ok = io:format("unknown message ~tp~n", [Unknown]), + j_loop(JS) + end. + +j_kbdln(Line, JS = #js{nsock = NS}) -> + FinalMsg = unicode:characters_to_binary([string:chomp(Line), "\r\n"]), + ok = enoise:send(NS, FinalMsg), + JS. + +% not the time for readline +% % user hit return, flush queue +% j_kbd("\r", JS = #js{kbdq = KQ, nsock = NS}) -> +% FinalMsg = unicode:characters_to_binary([KQ, "\r\n"]), +% ok = enoise:send(NS, FinalMsg), +% NewJS = JS#js{kbdq = []} +% NewJS; +% % user hit something else, add to queue +% % fixme: parse ANSI escape sequences here, maybe xterm +% j_kbd(Str1, JS = #js{kbdq = KQ}) -> +% JS#js{kbdq = KQ ++ Str1}. + + +j_received(Rcv, JS) -> + case takeln(Rcv) of + none -> + JS#js{rcv = Rcv}; + {line, Line, Rcv2} -> + ok = j_rln(Line), + j_received(Rcv2, JS) end. +j_rln(Line) -> + io:format("< ~ts~n", [string:chomp(Line)]). + +takeln(X) -> + takeln(<<>>, X). + +takeln(Acc, <<"\r\n", Rest/binary>>) -> + {line, <>, Rest}; +takeln(Acc, <>) -> + takeln(<>, Rest); +takeln(_Acc, <<>>) -> + none. + + +kbd_loop(Parent) -> + Line = io:get_line("> "), + Parent ! {kbd, line, Line}, + kbd_loop(Parent). + +%kbd_loop(Parent) -> +% Char = io:get_chars("", 1), +% Parent ! {kbd, Char}, +% kbd_loop(Parent). diff --git a/x04-noisy-echo/nechod b/x04-noisy-echo/nechod index 4c8be2e..008a14f 100755 --- a/x04-noisy-echo/nechod +++ b/x04-noisy-echo/nechod @@ -5,9 +5,10 @@ % ho from pimp pov -record(pho, - {pid :: pid(), - ref :: reference(), - name :: string()}). + {pid = none :: none | pid(), + ref = none :: none | reference(), + taken = false :: boolean(), + name = none :: none | string()}). %% pimp state -record(ps, @@ -25,10 +26,9 @@ main([]) -> - io:format("RBX example 4 telnet echo server daemon~n" - "`one' part of the many-to-one relationship~n", - []), - p_init(). + p_init(6969); +main([PortNum]) -> + p_init(list_to_integer(PortNum)). noise_options() -> @@ -43,9 +43,8 @@ noise_options() -> % pimp process startup -p_init() -> +p_init(Port) -> p_logln("pimp startup (p_init)"), - Port = 6969, Opts = [binary, {packet, 0}, {active, once}], case gen_tcp:listen(Port, Opts) of {ok, LSock} -> @@ -60,12 +59,12 @@ p_init() -> p_loop(PS = #ps{hos = Hos}) -> receive - {ho, _HoPid, taken} -> - NewPS = p_spawn_ho(PS), + {ho, HoPid, taken} -> + NewPS = p_taken(HoPid, PS), p_loop(NewPS); {ho, HoPid, sent, Data} -> HoName = p_ho_name(HoPid, PS), - SendMessage = io_lib:format("~ts :: ~ts\r\n", [HoName, Data]), + SendMessage = io_lib:format("~ts> ~ts\r\n", [HoName, Data]), ok = p_echo_except(HoPid, HoName, SendMessage, Hos), p_loop(PS); Down = {'DOWN', _, _, _, _} -> @@ -76,6 +75,20 @@ p_loop(PS = #ps{hos = Hos}) -> p_loop(PS) end. +p_taken(Pid, PS = #ps{hos = Hos}) -> + Hos_II = mark_taken(Pid, Hos, []), + PS_II = PS#ps{hos = Hos_II}, + PS_III = p_spawn_ho(PS_II), + PS_III. + +mark_taken(Pid, [Ho = #pho{pid = Pid} | Rest], Acc) -> + NewHo = Ho#pho{taken = true}, + lists:reverse(Acc) ++ [NewHo | Rest]; +mark_taken(Pid, [WrongHo | Rest], Acc) -> + mark_taken(Pid, Rest, [WrongHo | Acc]); +mark_taken(_, [], Acc) -> + lists:reverse(Acc). + % given pid of ho, get her name p_ho_name(HoPid, #ps{hos = Hos}) -> case lists:keyfind(HoPid, #pho.pid, Hos) of @@ -100,7 +113,8 @@ p_spawn_ho(PS = #ps{lsock = LSock, hos = Hos, ho_names = Names}) -> % ho down, remove her from roster p_ho_down({'DOWN', _Ref, process, Pid, Info}, PS = #ps{hos = Hos}) -> - ok = p_logfln("ho down: ~tp; reason: ~tw", [Pid, Info]), + Ho = lists:keyfind(Pid, #pho.pid, Hos), + ok = p_logfln("ho down: ~ts; reason: ~tw", [Ho#pho.name, Info]), NewHos = lists:keydelete(Pid, #pho.pid, Hos), NewPS = PS#ps{hos = NewHos}, NewPS. @@ -110,7 +124,10 @@ p_ho_down({'DOWN', _Ref, process, Pid, Info}, PS = #ps{hos = Hos}) -> % skip if equal p_echo_except(SrcPid, SrcName, Data, [#pho{pid = SrcPid} | Rest]) -> p_echo_except(SrcPid, SrcName, Data, Rest); -p_echo_except(SrcPid, SrcName, Data, [#pho{pid = DstPid} | Rest]) -> +% skip if not taken +p_echo_except(SrcPid, SrcName, Data, [#pho{taken = false} | Rest]) -> + p_echo_except(SrcPid, SrcName, Data, Rest); +p_echo_except(SrcPid, SrcName, Data, [#pho{pid = DstPid, taken = true} | Rest]) -> DstPid ! {pimp, send, Data}, p_echo_except(SrcPid, SrcName, Data, Rest); p_echo_except(_, _, _, []) -> @@ -147,7 +164,8 @@ h_loop(HS = #hs{name = Name, daddy = Daddy, asock = ASock, nsock = NSock}) -> ok = h_rcvd(Name, Data_II), Daddy ! {ho, self(), sent, Data_II}, h_loop(HS); - {pimp, send, Data} -> + {pimp, send, Data} = M -> + io:format("~p received ~p~n", [self(), M]), ok = h_send(Name, NSock, Data), h_loop(HS); {tcp_closed, ASock} -> @@ -160,7 +178,7 @@ h_loop(HS = #hs{name = Name, daddy = Daddy, asock = ASock, nsock = NSock}) -> % pimp log in bold p_logln(LogStr) -> - Msg = io_lib:format("!! Daddy :: ~ts~n", [LogStr]), + Msg = io_lib:format("Daddy: ~ts~n", [LogStr]), PimpChars = ?ANSI_BOLD([Msg]), io:put_chars(PimpChars). @@ -170,7 +188,7 @@ p_logfln(LogStr, Args) -> % ho log dimmed h_logln(HoName, LogStr) -> - FMsg = io_lib:format("!! ~ts :: ~ts~n", [HoName, LogStr]), + FMsg = io_lib:format("~ts: ~ts~n", [HoName, LogStr]), HoChars = ?ANSI_DIM([FMsg]), io:put_chars(HoChars). @@ -179,12 +197,15 @@ h_logfln(HoName, MsgStr, Args) -> % normal: john messages h_rcvd(Name, MsgStr) -> - Msg = io_lib:format("<< ~ts :: ~ts~n", [Name, MsgStr]), + Msg = io_lib:format("~ts< ~ts~n", [Name, string:chomp(MsgStr)]), io:put_chars(Msg). h_send(Name, NSock, Msg) -> - ok = io:format(">> ~ts :: ~ts~n", [Name, Msg]), - enoise:send(NSock, Msg). + FMsg = io_lib:format("~ts> ~ts~n", [Name, string:chomp(Msg)]), + HoChars = [?ANSI_DIM(FMsg)], + ok = io:put_chars(HoChars), + Send = unicode:characters_to_binary([string:chomp(Msg), "\r\n"]), + ok = enoise:send(NSock, Send). init_ho_names() ->