summaryrefslogtreecommitdiff
path: root/lib/chessh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chessh')
-rw-r--r--lib/chessh/client.ex0
-rw-r--r--lib/chessh/schema/key.ex17
-rw-r--r--lib/chessh/ssh/client.ex112
-rw-r--r--lib/chessh/ssh/renderers/menu.ex35
-rw-r--r--lib/chessh/ssh/tui.ex15
-rw-r--r--lib/chessh/utils.ex5
6 files changed, 163 insertions, 21 deletions
diff --git a/lib/chessh/client.ex b/lib/chessh/client.ex
deleted file mode 100644
index e69de29..0000000
--- a/lib/chessh/client.ex
+++ /dev/null
diff --git a/lib/chessh/schema/key.ex b/lib/chessh/schema/key.ex
index adf018d..df790e2 100644
--- a/lib/chessh/schema/key.ex
+++ b/lib/chessh/schema/key.ex
@@ -20,14 +20,6 @@ defmodule Chessh.Key do
|> validate_format(:key, ~r/^(?!ssh-dss).+/, message: "DSA keys are not supported")
end
- defp update_encode_key(attrs, field) do
- if Map.has_key?(attrs, field) do
- Map.update!(attrs, field, &encode_key/1)
- else
- attrs
- end
- end
-
def encode_key(key) do
if is_tuple(key) do
case key do
@@ -39,8 +31,15 @@ defmodule Chessh.Key do
else
key
end
- # Remove comment at end of key
|> String.replace(~r/ [^ ]+\@[^ ]+$/, "")
|> String.trim()
end
+
+ defp update_encode_key(attrs, field) do
+ if Map.has_key?(attrs, field) do
+ Map.update!(attrs, field, &encode_key/1)
+ else
+ attrs
+ end
+ end
end
diff --git a/lib/chessh/ssh/client.ex b/lib/chessh/ssh/client.ex
index eba188f..4eceb38 100644
--- a/lib/chessh/ssh/client.ex
+++ b/lib/chessh/ssh/client.ex
@@ -1,27 +1,125 @@
defmodule Chessh.SSH.Client do
alias IO.ANSI
+
require Logger
use GenServer
- @default_message [
+ @clear_codes [
ANSI.clear(),
ANSI.reset(),
- ANSI.home(),
- ["Hello, world"]
+ ANSI.home()
+ ]
+
+ @min_terminal_width 64
+ @min_terminal_height 31
+ @max_terminal_width 255
+ @max_terminal_height 127
+
+ @terminal_bad_dim_msg [
+ @clear_codes | "The dimensions of your terminal are not within in the valid range"
]
defmodule State do
defstruct tui_pid: nil,
- width: nil,
- height: nil,
+ width: 0,
+ height: 0,
player_session: nil,
- state_statck: []
+ buffer: [],
+ state_stack: [{&Chessh.SSH.Client.Menu.render/2, []}]
end
@impl true
def init([%State{tui_pid: tui_pid} = state]) do
- send(tui_pid, {:send_data, @default_message})
+ send(tui_pid, {:send_data, render(state)})
{:ok, state}
end
+
+ @impl true
+ def handle_info(:quit, %State{} = state) do
+ {:stop, :normal, state}
+ end
+
+ @impl true
+ def handle_info(msg, state) do
+ [burst_ms, burst_rate] =
+ Application.get_env(:chessh, RateLimits)
+ |> Keyword.take([:player_session_message_burst_ms, :player_session_message_burst_rate])
+ |> Keyword.values()
+
+ case Hammer.check_rate_inc(
+ "player-session-#{state.player_session.id}-burst-message-rate",
+ burst_ms,
+ burst_rate,
+ 1
+ ) do
+ {:allow, _count} ->
+ handle(msg, state)
+
+ {:deny, _limit} ->
+ {:noreply, state}
+ end
+ end
+
+ def handle({:data, data}, %State{tui_pid: tui_pid} = state) do
+ new_state =
+ keymap(data)
+ |> keypress(state)
+
+ send(tui_pid, {:send_data, render(new_state)})
+ {:noreply, new_state}
+ end
+
+ def handle({:resize, {width, height}}, %State{tui_pid: tui_pid} = state) do
+ new_state = %State{state | width: width, height: height}
+
+ if height <= @max_terminal_height || width <= @max_terminal_width do
+ send(tui_pid, {:send_data, render(new_state)})
+ end
+
+ {:noreply, new_state}
+ end
+
+ def keypress(:up, state), do: state
+ def keypress(:right, state), do: state
+ def keypress(:down, state), do: state
+ def keypress(:left, state), do: state
+
+ def keypress(:quit, state) do
+ send(self(), :quit)
+ state
+ end
+
+ def keypress(_, state), do: state
+
+ def keymap(key) do
+ case key do
+ # Exit keys - C-c and C-d
+ <<3>> -> :quit
+ <<4>> -> :quit
+ # Arrow keys
+ "\e[A" -> :up
+ "\e[B" -> :down
+ "\e[D" -> :left
+ "\e[C" -> :right
+ x -> x
+ end
+ end
+
+ @spec terminal_size_allowed(any, any) :: boolean
+ def terminal_size_allowed(width, height) do
+ Enum.member?(@min_terminal_width..@max_terminal_width, width) &&
+ Enum.member?(@min_terminal_height..@max_terminal_height, height)
+ end
+
+ defp render(%{width: width, height: height, state_stack: [{render_fn, args} | _tail]} = state) do
+ if terminal_size_allowed(width, height) do
+ [
+ @clear_codes ++
+ render_fn.(state, args)
+ ]
+ else
+ @terminal_bad_dim_msg
+ end
+ end
end
diff --git a/lib/chessh/ssh/renderers/menu.ex b/lib/chessh/ssh/renderers/menu.ex
new file mode 100644
index 0000000..c3c3646
--- /dev/null
+++ b/lib/chessh/ssh/renderers/menu.ex
@@ -0,0 +1,35 @@
+defmodule Chessh.SSH.Client.Menu do
+ alias Chessh.SSH.Client.State
+ alias Chessh.Utils
+
+ alias IO.ANSI
+
+ @logo " Simponic's
+
+ dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM
+ 88 M mmmmm..M M mmmmm..M M MMMMM MM
+.d8888b. 88d888b. .d8888b. M. `YM M. `YM M `M
+88' `\"\" 88' `88 88ooood8 MMMMMMM. M MMMMMMM. 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
+ MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM"
+
+ def render(
+ %State{width: width, height: height, state_stack: [_current_state | _tail]} = _state,
+ _args
+ ) do
+ {logo_width, logo_height} = Utils.text_dim(@logo)
+
+ split = String.split(@logo, "\n")
+
+ Enum.flat_map(
+ Enum.zip(0..(length(split) - 1), split),
+ fn {i, x} ->
+ [
+ ANSI.cursor(div(height - logo_height, 2) + i, div(width - logo_width, 2)),
+ "#{x}\n"
+ ]
+ end
+ )
+ end
+end
diff --git a/lib/chessh/ssh/tui.ex b/lib/chessh/ssh/tui.ex
index c0fc910..d76986f 100644
--- a/lib/chessh/ssh/tui.ex
+++ b/lib/chessh/ssh/tui.ex
@@ -64,8 +64,12 @@ defmodule Chessh.SSH.Tui do
end
end
- def handle_msg({:EXIT, client_pid, _reason}, %{client_pid: client_pid} = state) do
- {:stop, state.channel, state}
+ def handle_msg(
+ {:EXIT, client_pid, _reason},
+ %{client_pid: client_pid, channel_id: channel_id} = state
+ ) do
+ send(client_pid, :quit)
+ {:stop, channel_id, state}
end
def handle_msg(
@@ -94,7 +98,7 @@ defmodule Chessh.SSH.Tui do
state
) do
Logger.debug("DATA #{inspect(data)}")
- # send(state.client_pid, {:data, data})
+ send(state.client_pid, {:data, data})
{:ok, state}
end
@@ -126,10 +130,11 @@ defmodule Chessh.SSH.Tui do
def handle_ssh_msg(
{:ssh_cm, _connection_handler,
{:window_change, _channel_id, width, height, _pixwidth, _pixheight}},
- state
+ %{client_pid: client_pid} = state
) do
Logger.debug("WINDOW CHANGE")
- # Chessh.SSH.Client.resize(state.client_pid, width, height)
+ send(client_pid, {:resize, {width, height}})
+
{:ok,
%{
state
diff --git a/lib/chessh/utils.ex b/lib/chessh/utils.ex
index 1a7f8cf..3e83d5e 100644
--- a/lib/chessh/utils.ex
+++ b/lib/chessh/utils.ex
@@ -6,4 +6,9 @@ defmodule Chessh.Utils do
|> List.delete_at(-1)
|> to_string()
end
+
+ def text_dim(text) do
+ split = String.split(text, "\n")
+ {Enum.reduce(split, 0, fn x, acc -> max(acc, String.length(x)) end), length(split)}
+ end
end