Draw board #3
@ -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
|
||||||
|
@ -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,36 +53,43 @@ 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
|
||||||
) ++
|
) ++
|
||||||
Enum.flat_map(
|
Enum.flat_map(
|
||||||
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
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user