summaryrefslogtreecommitdiff
path: root/lib/chessh/ssh/client/client.ex
blob: a5f7bec82066f89b60791a79ce7a69d42720722e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
defmodule Chessh.SSH.Client do
  alias IO.ANSI
  alias Chessh.SSH.Client.Menu
  require Logger

  use GenServer

  @clear_codes [
    ANSI.clear(),
    ANSI.reset(),
    ANSI.home()
  ]

  @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: 0,
              height: 0,
              player_session: nil,
              screen_processes: []
  end

  @impl true
  def init([%State{tui_pid: tui_pid} = state]) do
    {:ok, screen_pid} =
      GenServer.start_link(Chessh.SSH.Client.Menu, [
        %Chessh.SSH.Client.Menu.State{tui_pid: tui_pid}
      ])

    {:ok, %{state | screen_processes: [screen_pid]}}
  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{width: width, height: height, screen_processes: [screen_pid | _]} = state
      ) do
    action = keymap(data)

    if action == :quit do
      {:stop, :normal, state}
    else
      send(screen_pid, {:input, width, height, action})
      {:noreply, state}
    end
  end

  #  def handle(
  #        {:refresh, },
  #        %State{screen_processes: [screen_pid | _] = screen_processes, width: width, height: height} = state
  #      ) do
  #    send(screen_pid, {:render, tui_pid, width, height})
  #    {:noreply, state}
  #  end

  def handle(
        {:resize, {width, height}},
        %State{tui_pid: tui_pid, screen_processes: [screen_pid | _]} = state
      ) do
    new_state = %State{state | width: width, height: height}

    if height <= @max_terminal_height && width <= @max_terminal_width do
      send(screen_pid, {:render, width, height})
    else
      send(tui_pid, {:send_data, @terminal_bad_dim_msg})
    end

    {:noreply, new_state}
  end

  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
      "\r" -> :return
      x -> x
    end
  end
end