Done moving menu to genserver architecture

This commit is contained in:
Simponic 2023-01-11 14:21:48 -07:00
parent 2ce03d4796
commit 628c6d95a3
Signed by untrusted user who does not match committer: simponic
GPG Key ID: 52B3774857EB24B1
4 changed files with 120 additions and 86 deletions

View File

@ -1,22 +1,18 @@
defmodule Chessh.SSH.Client do defmodule Chessh.SSH.Client do
alias IO.ANSI alias IO.ANSI
alias Chessh.SSH.Client.Menu
require Logger require Logger
use GenServer use GenServer
@clear_codes [ @clear_codes [
ANSI.clear(), ANSI.clear(),
ANSI.reset(),
ANSI.home() ANSI.home()
] ]
@max_terminal_width 255 @min_terminal_width 64
@max_terminal_height 127 @min_terminal_height 31
@max_terminal_width 200
@terminal_bad_dim_msg [ @max_terminal_height 100
@clear_codes | "The dimensions of your terminal are not within in the valid range"
]
defmodule State do defmodule State do
defstruct tui_pid: nil, defstruct tui_pid: nil,
@ -27,10 +23,10 @@ defmodule Chessh.SSH.Client do
end end
@impl true @impl true
def init([%State{tui_pid: tui_pid} = state]) do def init([%State{} = state]) do
{:ok, screen_pid} = {:ok, screen_pid} =
GenServer.start_link(Chessh.SSH.Client.Menu, [ GenServer.start_link(Chessh.SSH.Client.Menu, [
%Chessh.SSH.Client.Menu.State{tui_pid: tui_pid} %Chessh.SSH.Client.Menu.State{client_pid: self()}
]) ])
{:ok, %{state | screen_processes: [screen_pid]}} {:ok, %{state | screen_processes: [screen_pid]}}
@ -76,27 +72,36 @@ defmodule Chessh.SSH.Client do
end end
end end
# def handle( def handle(
# {:refresh, }, :refresh,
# %State{screen_processes: [screen_pid | _] = screen_processes, width: width, height: height} = state %State{} = state
# ) do ) do
# send(screen_pid, {:render, tui_pid, width, height}) render(state)
# {:noreply, state} {:noreply, state}
# end end
def handle(
{:send_to_ssh, data},
%State{width: width, height: height, tui_pid: tui_pid} = state
) do
case get_terminal_dim_msg(width, height) do
{true, msg} -> send(tui_pid, {:send_data, msg})
{false, _} -> send(tui_pid, {:send_data, data})
end
{:noreply, state}
end
def handle( def handle(
{:resize, {width, height}}, {:resize, {width, height}},
%State{tui_pid: tui_pid, screen_processes: [screen_pid | _]} = state %State{tui_pid: tui_pid, screen_processes: [screen_pid | _]} = state
) do ) do
new_state = %State{state | width: width, height: height} case get_terminal_dim_msg(width, height) do
{true, msg} -> send(tui_pid, {:send_data, msg})
if height <= @max_terminal_height && width <= @max_terminal_width do {false, _} -> send(screen_pid, {:render, width, height})
send(screen_pid, {:render, width, height})
else
send(tui_pid, {:send_data, @terminal_bad_dim_msg})
end end
{:noreply, new_state} {:noreply, %State{state | width: width, height: height}}
end end
def keymap(key) do def keymap(key) do
@ -113,4 +118,30 @@ defmodule Chessh.SSH.Client do
x -> x x -> x
end end
end end
defp get_terminal_dim_msg(width, height) do
case {height > @max_terminal_height, height < @min_terminal_height,
width > @max_terminal_width, width < @min_terminal_width} do
{true, _, _, _} -> {true, @clear_codes ++ ["The terminal height is too large."]}
{_, true, _, _} -> {true, @clear_codes ++ ["The terminal height is too small."]}
{_, _, true, _} -> {true, @clear_codes ++ ["The terminal width is too large"]}
{_, _, _, true} -> {true, @clear_codes ++ ["The terminal width is too small."]}
{false, false, false, false} -> {false, nil}
end
end
defp render(%State{
tui_pid: tui_pid,
width: width,
height: height,
screen_processes: [screen_pid | _]
}) do
{out_of_range, msg} = get_terminal_dim_msg(width, height)
if out_of_range && msg do
send(tui_pid, {:send_data, msg})
else
send(screen_pid, {:render, width, height})
end
end
end end

View File

@ -5,18 +5,10 @@ defmodule Chessh.SSH.Client.Menu do
require Logger require Logger
defmodule State do defmodule State do
defstruct dy: 0, defstruct client_pid: nil,
dx: 0,
tui_pid: nil,
selected: 0 selected: 0
end end
use Chessh.SSH.Client.Screen
def init([%State{} = state | _]) do
{:ok, state}
end
@logo " Simponic's @logo " Simponic's
dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM
88 M mmmmm..M M mmmmm..M M MMMMM MM 88 M mmmmm..M M mmmmm..M M MMMMM MM
@ -25,6 +17,11 @@ defmodule Chessh.SSH.Client.Menu do
88. ... 88 88 88. ... M. .MMM' M M. .MMM' M M MMMMM MM 88. ... 88 88 88. ... M. .MMM' M M. .MMM' M M MMMMM MM
`88888P' dP dP `88888P' Mb. .dM Mb. .dM M MMMMM MM `88888P' dP dP `88888P' Mb. .dM Mb. .dM M MMMMM MM
MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM" MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM"
use Chessh.SSH.Client.Screen
def init([%State{} = state | _]) do
{:ok, state}
end
# @options [ # @options [
# {"Option 1", {Chessh.SSH.Client.Board, [%Chessh.SSH.Client.Board.State{}]}}, # {"Option 1", {Chessh.SSH.Client.Board, [%Chessh.SSH.Client.Board.State{}]}},
@ -36,12 +33,7 @@ defmodule Chessh.SSH.Client.Menu do
{"Option 3", {}} {"Option 3", {}}
] ]
def handle_info({:render, width, height}, %State{} = state) do def input(width, height, action, %State{client_pid: client_pid, selected: selected} = state) do
render(width, height, state)
{:noreply, state}
end
def handle_info({:input, width, height, action}, %State{selected: selected} = state) do
new_state = new_state =
case(action) do case(action) do
:up -> :up ->
@ -61,21 +53,30 @@ defmodule Chessh.SSH.Client.Menu do
state state
end end
render(width, height, new_state) send(client_pid, {:send_to_ssh, render_state(width, height, new_state)})
{:noreply, new_state} new_state
end end
def render(width, height, %State{tui_pid: tui_pid, dy: dy, dx: dx, selected: selected}) do def render(width, height, %State{client_pid: client_pid} = state) do
text = String.split(@logo, "\n") send(client_pid, {:send_to_ssh, render_state(width, height, state)})
{logo_width, logo_height} = Utils.text_dim(@logo) state
{y, x} = center_rect({logo_width, logo_height + length(text)}, {width, height}) end
rendered = defp render_state(
width,
height,
%State{selected: selected}
) do
logo_lines = String.split(@logo, "\n")
{logo_width, logo_height} = Utils.text_dim(@logo)
{y, x} = center_rect({logo_width, logo_height + length(logo_lines)}, {width, height})
[ANSI.clear()] ++
Enum.flat_map( Enum.flat_map(
Enum.zip(1..length(text), text), Enum.zip(1..length(logo_lines), logo_lines),
fn {i, line} -> fn {i, line} ->
[ [
ANSI.cursor(y + i + dy, x + dx), ANSI.cursor(y + i, x),
line line
] ]
end end
@ -84,13 +85,11 @@ defmodule Chessh.SSH.Client.Menu do
Enum.zip(0..(length(@options) - 1), @options), Enum.zip(0..(length(@options) - 1), @options),
fn {i, {option, _}} -> fn {i, {option, _}} ->
[ [
ANSI.cursor(y + length(text) + i + dy, x + dx), ANSI.cursor(y + length(logo_lines) + i + 1, x),
if(i == selected, do: ANSI.format([:light_cyan, "* #{option}"]), else: option) if(i == selected, do: ANSI.format([:bright, :light_cyan, "+ #{option}"]), else: option)
] ]
end end
) ++ [ANSI.home()] ) ++ [ANSI.home()]
send(tui_pid, {:send_data, rendered})
end end
defp wrap_around(index, delta, length) do defp wrap_around(index, delta, length) do

View File

@ -1,21 +1,18 @@
defmodule Chessh.SSH.Client.Screen do defmodule Chessh.SSH.Client.Screen do
@callback handle_info( @callback render(width :: integer(), height :: integer(), state :: any()) :: any()
{:render, width :: integer(), height :: integer()}, @callback input(width :: integer(), height :: integer(), action :: any(), state :: any()) ::
state :: any() any()
) ::
{:noreply, any()}
@callback handle_info({:input, width :: integer(), height :: integer(), action :: any()}) ::
{:noreply, any()}
# @callback render(state :: Chessh.SSH.Client.State.t() | any()) :: any()
# @callback handle_input(action :: any(), state :: Chessh.SSH.Client.State.t()) ::
# Chessh.SSH.Client.State.t()
defmacro __using__(_) do defmacro __using__(_) do
quote do quote do
@behaviour Chessh.SSH.Client.Screen @behaviour Chessh.SSH.Client.Screen
use GenServer use GenServer
@clear_codes [
IO.ANSI.clear(),
IO.ANSI.home()
]
@ascii_chars Application.compile_env!(:chessh, :ascii_chars_json_file) @ascii_chars Application.compile_env!(:chessh, :ascii_chars_json_file)
|> File.read!() |> File.read!()
|> Jason.decode!() |> Jason.decode!()
@ -26,6 +23,12 @@ defmodule Chessh.SSH.Client.Screen do
div(parent_width - rect_width, 2) div(parent_width - rect_width, 2)
} }
end end
def handle_info({:render, width, height}, state),
do: {:noreply, render(width, height, state)}
def handle_info({:input, width, height, action}, state),
do: {:noreply, input(width, height, action, state)}
end end
end end
end end

View File

@ -26,7 +26,7 @@ defmodule Chessh.SSH.Tui do
{:ok, init_state} {:ok, init_state}
end end
def handle_msg({:ssh_channel_up, channel_id, connection_ref}, state) do def handle_msg({:ssh_channel_up, channel_id, connection_ref}, %State{} = state) do
Logger.debug("SSH channel up #{inspect(:ssh.connection_info(connection_ref))}") Logger.debug("SSH channel up #{inspect(:ssh.connection_info(connection_ref))}")
connected_player = connected_player =
@ -54,7 +54,7 @@ defmodule Chessh.SSH.Tui do
:syn.join(:player_sessions, {:session, session.id}, self()) :syn.join(:player_sessions, {:session, session.id}, self())
{:ok, {:ok,
%{ %State{
state state
| channel_id: channel_id, | channel_id: channel_id,
connection_ref: connection_ref, connection_ref: connection_ref,
@ -66,7 +66,7 @@ defmodule Chessh.SSH.Tui do
def handle_msg( def handle_msg(
{:EXIT, client_pid, _reason}, {:EXIT, client_pid, _reason},
%{client_pid: client_pid, channel_id: channel_id} = state %State{client_pid: client_pid, channel_id: channel_id} = state
) do ) do
send(client_pid, :quit) send(client_pid, :quit)
{:stop, channel_id, state} {:stop, channel_id, state}
@ -74,7 +74,7 @@ defmodule Chessh.SSH.Tui do
def handle_msg( def handle_msg(
{:send_data, data}, {:send_data, data},
%{connection_ref: connection_ref, channel_id: channel_id} = state %State{connection_ref: connection_ref, channel_id: channel_id} = state
) do ) do
:ssh_connection.send(connection_ref, channel_id, data) :ssh_connection.send(connection_ref, channel_id, data)
{:ok, state} {:ok, state}
@ -82,7 +82,7 @@ defmodule Chessh.SSH.Tui do
def handle_msg( def handle_msg(
:session_closed, :session_closed,
%{connection_ref: connection_ref, channel_id: channel_id} = state %State{connection_ref: connection_ref, channel_id: channel_id} = state
) do ) do
:ssh_connection.send(connection_ref, channel_id, @session_closed_message) :ssh_connection.send(connection_ref, channel_id, @session_closed_message)
{:stop, channel_id, state} {:stop, channel_id, state}
@ -94,7 +94,7 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, _connection_handler, {:data, _channel_id, _type, data}}, {:ssh_cm, _connection_handler, {:data, _channel_id, _type, data}},
state %State{} = state
) do ) do
send(state.client_pid, {:data, data}) send(state.client_pid, {:data, data})
{:ok, state} {:ok, state}
@ -103,7 +103,7 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, connection_handler, {:ssh_cm, connection_handler,
{:pty, channel_id, want_reply?, {_term, width, height, _pixwidth, _pixheight, _opts}}}, {:pty, channel_id, want_reply?, {_term, width, height, _pixwidth, _pixheight, _opts}}},
state %State{client_pid: client_pid} = state
) do ) do
Logger.debug("#{inspect(state.player_session)} has requested a PTY") Logger.debug("#{inspect(state.player_session)} has requested a PTY")
:ssh_connection.reply_request(connection_handler, want_reply?, :success, channel_id) :ssh_connection.reply_request(connection_handler, want_reply?, :success, channel_id)
@ -128,12 +128,12 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, _connection_handler, {:ssh_cm, _connection_handler,
{:window_change, _channel_id, width, height, _pixwidth, _pixheight}}, {:window_change, _channel_id, width, height, _pixwidth, _pixheight}},
%{client_pid: client_pid} = state %State{client_pid: client_pid} = state
) do ) do
send(client_pid, {:resize, {width, height}}) send(client_pid, {:resize, {width, height}})
{:ok, {:ok,
%{ %State{
state state
| width: width, | width: width,
height: height height: height
@ -157,12 +157,13 @@ defmodule Chessh.SSH.Tui do
} }
]) ])
{:ok, %{state | client_pid: client_pid}} send(client_pid, :refresh)
{:ok, %State{state | client_pid: client_pid}}
end end
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, connection_handler, {:exec, channel_id, want_reply?, cmd}}, {:ssh_cm, connection_handler, {:exec, channel_id, want_reply?, cmd}},
state %State{} = state
) do ) do
:ssh_connection.reply_request(connection_handler, want_reply?, :success, channel_id) :ssh_connection.reply_request(connection_handler, want_reply?, :success, channel_id)
Logger.debug("EXEC #{cmd}") Logger.debug("EXEC #{cmd}")
@ -171,7 +172,7 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, _connection_handler, {:eof, _channel_id}}, {:ssh_cm, _connection_handler, {:eof, _channel_id}},
state %State{} = state
) do ) do
Logger.debug("EOF") Logger.debug("EOF")
{:ok, state} {:ok, state}
@ -179,7 +180,7 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, _connection_handler, {:signal, _channel_id, signal}}, {:ssh_cm, _connection_handler, {:signal, _channel_id, signal}},
state %State{} = state
) do ) do
Logger.debug("SIGNAL #{signal}") Logger.debug("SIGNAL #{signal}")
{:ok, state} {:ok, state}
@ -187,7 +188,7 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, _connection_handler, {:exit_signal, channel_id, signal, err, lang}}, {:ssh_cm, _connection_handler, {:exit_signal, channel_id, signal, err, lang}},
state %State{} = state
) do ) do
Logger.debug("EXIT SIGNAL #{signal} #{err} #{lang}") Logger.debug("EXIT SIGNAL #{signal} #{err} #{lang}")
{:stop, channel_id, state} {:stop, channel_id, state}
@ -195,7 +196,7 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
{:ssh_cm, _connection_handler, {:exit_STATUS, channel_id, status}}, {:ssh_cm, _connection_handler, {:exit_STATUS, channel_id, status}},
state %State{} = state
) do ) do
Logger.debug("EXIT STATUS #{status}") Logger.debug("EXIT STATUS #{status}")
{:stop, channel_id, state} {:stop, channel_id, state}
@ -203,7 +204,7 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg( def handle_ssh_msg(
msg, msg,
%{channel_id: channel_id} = state %State{channel_id: channel_id} = state
) do ) do
Logger.debug("UNKOWN MESSAGE #{inspect(msg)}") Logger.debug("UNKOWN MESSAGE #{inspect(msg)}")
{:stop, channel_id, state} {:stop, channel_id, state}