Mon, 15 Dec 2014 01:41:06 +0200
- now exits cleanly with ^Q!
- the statusbar now actually features meaningful info (IP, num admins, current level) and is colored to match titlebar
--- a/sources/basics.h Sun Dec 14 23:41:00 2014 +0200 +++ b/sources/basics.h Mon Dec 15 01:41:06 2014 +0200 @@ -63,9 +63,12 @@ using Function = std::function<Signature>; FUNCTION print_to_console (const String& a) -> void; +FUNCTION request_exit() -> void; template<typename T> inline FUNCTION clamp (T a, T b, T c) -> T { return (a < b) ? b : (a > c) ? c : a; } + +struct Exitception {};
--- a/sources/interface.cpp Sun Dec 14 23:41:00 2014 +0200 +++ b/sources/interface.cpp Mon Dec 15 01:41:06 2014 +0200 @@ -50,13 +50,14 @@ static bool g_needStatusBarRender = false; static bool g_needInputRender = false; static bool g_needOutputRender = false; -static String g_statusBarText; static struct { char ch; int x; } g_cursorChar; static Vector<String> g_output = {""}; static int g_outputScroll = 0; static String g_title; static InputState g_inputState = INPUTSTATE_NORMAL; +static Function<void (void)> g_disconnectConfirmFunction = nullptr; static IPAddress g_address; +static String g_statusBarText; // ------------------------------------------------------------------------------------------------- // @@ -86,6 +87,33 @@ // ------------------------------------------------------------------------------------------------- // +static FUNCTION +set_input_state (InputState newstate) -> void +{ + // Clear the input row (unless going to or from confirm state) + if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION + and g_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) + { + g_input.clear(); + } + + switch (newstate) + { + case INPUTSTATE_ADDRESS: + if (g_address.host != 0) + g_input = g_address.to_string (IP_WITH_PORT); + break; + + default: + break; + } + + g_inputState = newstate; + g_needInputRender = true; +} + +// ------------------------------------------------------------------------------------------------- +// FUNCTION Interface::initialize() -> void { @@ -111,13 +139,6 @@ g_needRefresh = false; print ("Interface initialized.\n"); } -// ------------------------------------------------------------------------------------------------- -// -static FUNCTION -interface_sessions_width() -> int -{ - return COLS / 3; -} // ------------------------------------------------------------------------------------------------- // @@ -151,6 +172,21 @@ // ------------------------------------------------------------------------------------------------- // static FUNCTION +safe_disconnect (Function<void()> afterwards) -> void +{ + if (RCONSession::get_session() != nullptr + and RCONSession::get_session()->state() != RCON_DISCONNECTED) + { + g_disconnectConfirmFunction = afterwards; + set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); + } + else + afterwards(); +} + +// ------------------------------------------------------------------------------------------------- +// +static FUNCTION interface_render_output() -> void { int height = LINES - 3; @@ -233,20 +269,62 @@ static FUNCTION interface_render_statusbar() -> void { + int color = interface_color_pair (WHITE, BLUE); int y = LINES - 1; + attron (color); mvhline (y, 0, ' ', COLS); mvprintw (y, 0, "%s", g_statusBarText.chars()); + attroff (color); g_needRefresh = true; g_needStatusBarRender = false; } // ------------------------------------------------------------------------------------------------- // -static FUNCTION -set_statusbar_text (const String& a) -> void +FUNCTION +Interface::update_statusbar() -> void { - g_statusBarText = a; - g_needStatusBarRender = true; + String text; + RCONSession* session = RCONSession::get_session(); + + if (session == nullptr) + { + text = "[DISCONNECTED]"; + } + else + { + switch (session->state()) + { + case RCON_DISCONNECTED: + text = "[DISCONNECTED]"; + break; + + case RCON_CONNECTING: + case RCON_AUTHENTICATING: + text = "Connecting to " + session->address().to_string (IP_WITH_PORT) + "..."; + break; + + case RCON_CONNECTED: + { + String adminText; + + if (session->num_admins() == 0) + adminText = "No other admins"; + else + adminText = format ("%1 other admin%s1", session->num_admins()); + + text = format ("%1 | %2 | %3", session->address().to_string (IP_WITH_PORT), + session->level(), adminText); + } + break; + } + } + + if (text != g_statusBarText) + { + g_statusBarText = text; + g_needStatusBarRender = true; + } } // ------------------------------------------------------------------------------------------------- @@ -254,6 +332,7 @@ FUNCTION Interface::render_full() -> void { + update_statusbar(); interface_render_titlebar(); interface_render_output(); interface_render_statusbar(); @@ -279,43 +358,15 @@ // ------------------------------------------------------------------------------------------------- // -static FUNCTION -set_input_state (InputState newstate) -> void -{ - // Clear the input row (unless going to or from confirm state) - if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION - and g_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) - { - g_input.clear(); - } - - switch (newstate) - { - case INPUTSTATE_ADDRESS: - if (g_address.host != 0) - g_input = g_address.to_string (IP_WITH_PORT); - break; - - default: - break; - } - - g_inputState = newstate; - g_needInputRender = true; -} - -// ------------------------------------------------------------------------------------------------- -// FUNCTION Interface::handle_input() -> void { int ch = ::getch(); - set_statusbar_text (String::from_number (ch)); if (g_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) { if (ch == 'y' or ch == 'Y') - set_input_state (INPUTSTATE_ADDRESS); + g_disconnectConfirmFunction(); else if (ch == 'n' or ch == 'N') set_input_state (INPUTSTATE_NORMAL); return; @@ -328,9 +379,34 @@ } else switch (ch) { - case KEY_F(1): - endwin(); - exit (EXIT_SUCCESS); + case 'Q' - 'A' + 1: // ^Q + switch (g_inputState) + { + case INPUTSTATE_CONFIRM_DISCONNECTION: + break; + + case INPUTSTATE_NORMAL: + safe_disconnect ([]() + { + endwin(); + throw Exitception(); + }); + break; + + case INPUTSTATE_PASSWORD: + set_input_state (INPUTSTATE_ADDRESS); + break; + + case INPUTSTATE_ADDRESS: + set_input_state (INPUTSTATE_NORMAL); + } + break; + + case '\e': + if (g_inputState == INPUTSTATE_PASSWORD) + set_input_state (INPUTSTATE_ADDRESS); + else if (g_inputState == INPUTSTATE_ADDRESS) + set_input_state (INPUTSTATE_NORMAL); break; case KEY_LEFT: @@ -438,17 +514,7 @@ case 'N' - 'A' + 1: // ^N if (g_inputState == INPUTSTATE_NORMAL) - { - if (RCONSession::get_session() != nullptr - and RCONSession::get_session()->state() != RCON_DISCONNECTED) - { - set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); - } - else - { - set_input_state (INPUTSTATE_ADDRESS); - } - } + safe_disconnect ([]() {set_input_state (INPUTSTATE_ADDRESS);}); break; } }
--- a/sources/interface.h Sun Dec 14 23:41:00 2014 +0200 +++ b/sources/interface.h Mon Dec 15 01:41:06 2014 +0200 @@ -38,4 +38,5 @@ FUNCTION render() -> void; FUNCTION render_full() -> void; FUNCTION set_title (const String& message) -> void; + FUNCTION update_statusbar() -> void; };
--- a/sources/main.cpp Sun Dec 14 23:41:00 2014 +0200 +++ b/sources/main.cpp Mon Dec 15 01:41:06 2014 +0200 @@ -35,6 +35,8 @@ #include "huffman/huffman.h" #include "interface.h" +static bool g_shouldExit = false; + // ------------------------------------------------------------------------------------------------- // FUNCTION @@ -43,35 +45,53 @@ HUFFMAN_Construct(); Interface::initialize(); - for (;;) + try { - fd_set fdset; - int highest = 0; - timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 250000; // 0.25 seconds - FD_ZERO (&fdset); - FD_SET (0, &fdset); - RCONSession* session = RCONSession::get_session(); - - if (session) + for (;;) { - int fd = session->socket()->file_descriptor(); - highest = max (highest, fd); - FD_SET (fd, &fdset); - } + if (g_shouldExit) + break; - select (highest + 1, &fdset, nullptr, nullptr, &timeout); + fd_set fdset; + int highest = 0; + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 250000; // 0.25 seconds + FD_ZERO (&fdset); + FD_SET (0, &fdset); + RCONSession* session = RCONSession::get_session(); - if (FD_ISSET (0, &fdset)) - // stdin is ready, what's incoming? - Interface::handle_input(); + if (session) + { + int fd = session->socket()->file_descriptor(); + highest = max (highest, fd); + FD_SET (fd, &fdset); + } + + select (highest + 1, &fdset, nullptr, nullptr, &timeout); - if (session) - session->tick(); + if (FD_ISSET (0, &fdset)) + // stdin is ready, what's incoming? + Interface::handle_input(); + + if (session) + session->tick(); - Interface::render(); + Interface::render(); + } } + catch (const Exitception&) {} + + if (RCONSession::get_session()) + RCONSession::get_session()->disconnect(); return EXIT_SUCCESS; } + +// ------------------------------------------------------------------------------------------------- +// +FUNCTION +request_exit() -> void +{ + g_shouldExit = true; +}
--- a/sources/network/rconsession.cpp Sun Dec 14 23:41:00 2014 +0200 +++ b/sources/network/rconsession.cpp Mon Dec 15 01:41:06 2014 +0200 @@ -7,7 +7,8 @@ // RCONSession::RCONSession() : m_state (RCON_DISCONNECTED), - m_lastPing (0) + m_lastPing (0), + m_numAdmins (0) { if (g_rconSession != NULL) { @@ -40,6 +41,8 @@ // RCONSession::~RCONSession() { + disconnect(); + if (g_rconSession == this) g_rconSession = nullptr; } @@ -51,6 +54,7 @@ { m_address = address; m_state = RCON_CONNECTING; + Interface::update_statusbar(); } // ------------------------------------------------------------------------------------------------- @@ -58,13 +62,14 @@ METHOD RCONSession::disconnect() -> void { - if (m_state == RCON_CONNECTED) + if (m_state > RCON_CONNECTING) { // Say goodbye to remote Bytestream packet; packet.write_byte (CLRC_DISCONNECT); this->send (packet); print ("Disconnected from %1\n", m_address.to_string (IP_WITH_PORT)); + Interface::update_statusbar(); } m_state = RCON_DISCONNECTED; @@ -202,11 +207,13 @@ break; case SVRCU_ADMINCOUNT: - print ("Admin count: %d1\n", packet.read_byte()); + m_numAdmins = packet.read_byte(); + Interface::update_statusbar(); break; case SVRCU_MAP: - print ("New level: %1\n", packet.read_string()); + m_level = packet.read_string(); + Interface::update_statusbar(); break; } } @@ -295,3 +302,27 @@ { return m_state; } + +// ------------------------------------------------------------------------------------------------- +// +METHOD +RCONSession::address() const -> const IPAddress& +{ + return m_address; +} + +// ------------------------------------------------------------------------------------------------- +// +METHOD +RCONSession::num_admins() const -> int +{ + return m_numAdmins; +} + +// ------------------------------------------------------------------------------------------------- +// +METHOD +RCONSession::level() const -> const String& +{ + return m_level; +}
--- a/sources/network/rconsession.h Sun Dec 14 23:41:00 2014 +0200 +++ b/sources/network/rconsession.h Mon Dec 15 01:41:06 2014 +0200 @@ -90,10 +90,12 @@ public: ~RCONSession(); + METHOD address() const -> const IPAddress&; METHOD connect (IPAddress address) -> void; METHOD disconnect() -> void; METHOD handle_packet (Bytestream& packet, const IPAddress& from) -> void; METHOD process_server_updates (Bytestream& packet) -> void; + METHOD num_admins() const -> int; METHOD send (const Bytestream& packet) -> void; METHOD send_hello() -> void; METHOD send_password() -> void; @@ -103,6 +105,7 @@ METHOD bump_last_ping() -> void; METHOD send_command (const String& message) -> bool; METHOD state() const -> RCONSessionState; + METHOD level() const -> const String&; static METHOD new_session() -> RCONSession*; static METHOD get_session() -> RCONSession*; @@ -118,4 +121,6 @@ String m_salt; int m_serverProtocol; String m_hostname; + int m_numAdmins; + String m_level; };