Merged with default protocol5

Wed, 20 Jul 2016 12:55:39 +0300

author
Teemu Piippo <teemu@compsta2.com>
date
Wed, 20 Jul 2016 12:55:39 +0300
branch
protocol5
changeset 131
4996c8684b93
parent 130
9f54db6f9922 (current diff)
parent 129
a556ce001e26 (diff)
child 133
4d8fa5394d67

Merged with default

sources/coloredline.cpp file | annotate | diff | comparison | revisions
sources/coloredline.h file | annotate | diff | comparison | revisions
sources/interface.cpp file | annotate | diff | comparison | revisions
sources/interface.h file | annotate | diff | comparison | revisions
sources/mystring.cpp file | annotate | diff | comparison | revisions
sources/mystring.h file | annotate | diff | comparison | revisions
sources/network/rconsession.cpp file | annotate | diff | comparison | revisions
sources/network/rconsession.h file | annotate | diff | comparison | revisions
sources/network/udpsocket.cpp file | annotate | diff | comparison | revisions
--- a/.hgtags	Mon Jan 25 04:15:31 2016 +0200
+++ b/.hgtags	Wed Jul 20 12:55:39 2016 +0300
@@ -1,1 +1,3 @@
 5e968dc8d552598f5bb5343bd5fa0c24088b6ee9 1.0
+9d640b94ef020c61359bedfd93831c935b904b2b 1.1
+558278b282a08b7563423b399e0d5f2f36004625 test
--- a/LICENSE	Mon Jan 25 04:15:31 2016 +0200
+++ b/LICENSE	Wed Jul 20 12:55:39 2016 +0300
@@ -1,4 +1,4 @@
-Copyright 2014, 2015 Teemu Piippo
+Copyright 2014 - 2016 Teemu Piippo
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -51,4 +51,4 @@
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file
+THE SOFTWARE.
--- a/sources/basics.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/basics.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -29,6 +29,8 @@
 */
 
 #pragma once
+#include <stdlib.h>
+
 #if !defined(_MSC_VER) && !defined(__cdecl)
 # define __cdecl
 #endif
@@ -88,6 +90,10 @@
 	return (a < b) ? b : (a > c) ? c : a;
 }
 
+template <typename T, size_t N>
+char (&_ArraySizeHelper(T (&array)[N]))[N];
+#define countof(array) (sizeof(_ArraySizeHelper( array )))
+
 struct Exitception {};
 
 END_ZFC_NAMESPACE
--- a/sources/coloredline.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/coloredline.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -105,23 +105,48 @@
 		if (m_boldActive)
 			m_data << RLINE_OFF_BOLD;
 
+		m_boldActive = false;
+
 		// Chars may be in uppercase
 		if (ch >= 'A' and ch <= 'V')
 			ch += 'a' - 'A';
 
 		if (ch >= 'a' and ch <= 'v' and ch != 'l')
 		{
-			auto colorInfo = colorCodes[ch - 'a'];
-			m_activeColor = colorInfo.color;
-			m_boldActive = colorInfo.bold;
-			assert (m_activeColor < 8);
-			set_color (m_activeColor, true);
-
-			if (m_boldActive)
-				m_data << RLINE_ON_BOLD;
+			const ColorCodeInfo& colorInfo = colorCodes[ch - 'a'];
+			activate_color(colorInfo.color, colorInfo.bold);
 		}
 
-		m_colorCodeStage = 0;
+		if (ch == '[')
+			m_colorCodeStage = 2;
+		else
+			m_colorCodeStage = 0;
+		return;
+	}
+	else if (m_colorCodeStage == 2)
+	{
+		if (ch == ']')
+		{
+			String color = m_incomingColorName.to_lowercase();
+
+			for (size_t i = 0; i < countof(colorCodes); ++i)
+			{
+				const ColorCodeInfo& colorInfo = colorCodes[i];
+
+				if (String(colorInfo.name).to_lowercase() == color)
+				{
+					activate_color(colorInfo.color, colorInfo.bold);
+					m_colorCodeStage = 0;
+					break;
+				}
+			}
+
+			m_incomingColorName = "";
+			m_colorCodeStage = 0;
+		}
+		else if (isprint(ch))
+			m_incomingColorName += ch;
+
 		return;
 	}
 
@@ -143,6 +168,22 @@
 
 // -------------------------------------------------------------------------------------------------
 //
+void ColoredLine::activate_color (Color color, bool bold)
+{
+	if (m_boldActive)
+		m_data << RLINE_OFF_BOLD;
+
+	m_activeColor = color;
+	m_boldActive = bold;
+	assert (m_activeColor < 8);
+	set_color (m_activeColor, true);
+
+	if (m_boldActive)
+		m_data << RLINE_ON_BOLD;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
 void ColoredLine::set_color (Color a, bool on)
 {
 	assert (a < 8);
--- a/sources/coloredline.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/coloredline.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -74,6 +74,7 @@
 	int rows (int cols) const;
 
 private:
+	void activate_color (Color color, bool bold);
 	void set_color (Color a, bool on);
 
 	Vector<int> m_data;
@@ -83,6 +84,7 @@
 	bool m_boldActive;
 	int m_colorCodeStage;
 	String m_string;
+	String m_incomingColorName;
 };
 
 END_ZFC_NAMESPACE
--- a/sources/geometry.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/geometry.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/interface.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/interface.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -37,20 +37,23 @@
 #include "coloredline.h"
 BEGIN_ZFC_NAMESPACE
 
-static const int g_pageSize = 10;
+static const int PAGE_SIZE = 10;
 
 // -------------------------------------------------------------------------------------------------
 //
 chtype Interface::color_pair (Color fg, Color bg)
 {
-	return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg));
+	if (fg == DEFAULT && bg == DEFAULT)
+		return 0;
+	else
+		return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg));
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 const String& Interface::current_input()
 {
-	return InputHistory[InputCursor];
+	return m_inputHistory[m_inputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -59,10 +62,10 @@
 //
 void Interface::detach_input()
 {
-	if (InputCursor > 0)
+	if (m_inputCursor > 0)
 	{
-		InputHistory[0] = current_input();
-		InputCursor = 0;
+		m_inputHistory[0] = current_input();
+		m_inputCursor = 0;
 	}
 }
 
@@ -72,7 +75,7 @@
 String& Interface::mutable_current_input()
 {
 	detach_input();
-	return InputHistory[InputCursor];
+	return m_inputHistory[m_inputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -80,19 +83,19 @@
 void Interface::move_input_cursor (int delta)
 {
 	// No input history when inputting addresses or passwords
-	if (CurrentInputState != INPUTSTATE_NORMAL)
+	if (m_inputState != INPUTSTATE_NORMAL)
 	{
-		InputCursor = 0;
+		m_inputCursor = 0;
 		return;
 	}
 
-	int oldcursor = InputCursor;
-	InputCursor = clamp (InputCursor + delta, 0, InputHistory.size() - 1);
+	int oldcursor = m_inputCursor;
+	m_inputCursor = clamp (m_inputCursor + delta, 0, m_inputHistory.size() - 1);
 
-	if (InputCursor != oldcursor)
+	if (m_inputCursor != oldcursor)
 	{
-		CursorPosition = current_input().length();
-		NeedInputRender = true;
+		m_cursorPosition = current_input().length();
+		m_needInputRender = true;
 	}
 }
 
@@ -102,7 +105,7 @@
 {
 	String prompt;
 
-	switch (CurrentInputState)
+	switch (m_inputState)
 	{
 	case INPUTSTATE_NORMAL: prompt = ">"; break;
 	case INPUTSTATE_ADDRESS: prompt = "address:"; break;
@@ -119,41 +122,41 @@
 {
 	// Clear the input row (unless going to or from confirm state)
 	if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION
-		and CurrentInputState != INPUTSTATE_CONFIRM_DISCONNECTION)
+		and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
-		InputCursor = 0;
+		m_inputCursor = 0;
 		mutable_current_input().clear();
 	}
 
 	switch (newstate)
 	{
 	case INPUTSTATE_ADDRESS:
-		if (CurrentAddress.host != 0)
-			mutable_current_input() = CurrentAddress.to_string (IPAddress::WITH_PORT);
+		if (m_remoteAddress.host != 0)
+			mutable_current_input() = m_remoteAddress.to_string (IPAddress::WITH_PORT);
 		break;
 
 	default:
 		break;
 	}
 
-	CurrentInputState = newstate;
-	NeedInputRender = true;
+	m_inputState = newstate;
+	m_needInputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 Interface::Interface() :
-	InputCursor (0),
-	CursorPosition (0),
-	InputPanning (0),
-	NeedRefresh (false),
-	NeedStatusBarRender (false),
-	NeedInputRender (false),
-	NeedOutputRender (false),
-	NeedNicklistRender (false),
-	OutputScroll (0),
-	CurrentInputState (INPUTSTATE_NORMAL),
-	DisconnectConfirmFunction (nullptr)
+	m_inputCursor (0),
+	m_cursorPosition (0),
+	m_inputPanning (0),
+	m_needRefresh (false),
+	m_needStatusBarRender (false),
+	m_needInputRender (false),
+	m_needOutputRender (false),
+	m_needNicklistRender (false),
+	m_outputScroll (0),
+	m_inputState (INPUTSTATE_NORMAL),
+	m_disconnectCallback (nullptr)
 {
 	::initscr();
 	::raw();
@@ -161,20 +164,19 @@
 	::noecho();
 	::refresh();
 	::timeout (0);
-	InputHistory.clear();
-	InputHistory << "";
-	OutputLines.clear();
-	OutputLines << ColoredLine();
-	Session.set_interface (this);
-	Title.sprintf (APPNAME " %s (%s)", full_version_string(), changeset_date_string());
+	m_inputHistory.clear();
+	m_inputHistory << "";
+	m_outputLines.clear();
+	m_outputLines << ColoredLine();
+	m_session.set_interface (this);
+	reset_title();
 
 	if (::has_colors())
 	{
 		::start_color();
-		::use_default_colors();
-
-		int defaultFg = (use_default_colors() == OK) ? -1 : COLOR_WHITE;
-		int defaultBg = (use_default_colors() == OK) ? -1 : COLOR_BLACK;
+		bool hasDefaultColors = (::use_default_colors() == OK);
+		int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE;
+		int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK;
 
 		// Initialize color pairs
 		for (int i = 0; i < NUM_COLORS; ++i)
@@ -184,58 +186,57 @@
 			int fg = (i == DEFAULT) ? defaultFg : i;
 			int bg = (j == DEFAULT) ? defaultBg : j;
 
-			if (::init_pair (pairnum, fg, bg) == ERR)
-				print ("Unable to initialize color pair %d (%d, %d)\n", pairnum, fg, bg);
+			if (fg != -1 || bg != -1)
+			{
+				if (::init_pair (pairnum, fg, bg) == ERR)
+					print_warning ("Unable to initialize color pair %d (%d, %d)\n", pairnum, fg, bg);
+			}
 		}
 	}
-	else
-	{
-		print ("This terminal does not appear to support colors.\n");
-	}
 
 	render_full();
 	refresh();
-	NeedRefresh = false;
+	m_needRefresh = false;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::render_titlebar()
 {
-	if (Title.length() <= COLS)
+	if (m_title.length() <= COLS)
 	{
 		chtype pair = color_pair (WHITE, BLUE);
-		int startx = (COLS - Title.length()) / 2;
-		int endx = startx + Title.length();
+		int startx = (COLS - m_title.length()) / 2;
+		int endx = startx + m_title.length();
 		attron (pair);
-		mvprintw (0, startx, "%s", Title.chars());
+		mvprintw (0, startx, "%s", m_title.chars());
 		mvhline (0, 0, ' ', startx);
 		mvhline (0, endx, ' ', COLS - endx);
 		attroff (pair);
 	}
 
-	NeedRefresh = true;
+	m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::set_title (const String& title)
 {
-	Title = title;
+	m_title = title;
 	render_titlebar();
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::safe_disconnect (std::function<void()> afterwards)
+void Interface::safe_disconnect (std::function<void(bool)> afterwards)
 {
-	if (Session.is_active())
+	if (m_session.is_active())
 	{
-		DisconnectConfirmFunction = afterwards;
+		m_disconnectCallback = afterwards;
 		set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION);
 	}
 	else
-		afterwards();
+		afterwards(false);
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -302,15 +303,15 @@
 //
 void Interface::render_output()
 {
-	if (OutputLines.size() == 1)
+	if (m_outputLines.size() == 1)
 		return;
 
-	OutputScroll = clamp (OutputScroll, 0, OutputLines.size() - 1);
+	m_outputScroll = clamp (m_outputScroll, 0, m_outputLines.size() - 1);
 
 	int height = LINES - 3;
 	int width = COLS - nicklist_width();
 	int printOffset = 0;
-	int end = OutputLines.size() - 1 - OutputScroll;
+	int end = m_outputLines.size() - 1 - m_outputScroll;
 	int start = end;
 	int usedHeight = 0;
 	int y = 1;
@@ -319,7 +320,7 @@
 	// Where to start?
 	while (start > 0)
 	{
-		int rows = OutputLines[start - 1].rows (width);
+		int rows = m_outputLines[start - 1].rows (width);
 
 		if (usedHeight + rows > height)
 		{
@@ -335,9 +336,9 @@
 	// See if there's any more rows to use (end may be too small)
 	if (not tightFit)
 	{
-		while (end < OutputLines.size())
+		while (end < m_outputLines.size())
 		{
-			int rows = OutputLines[end].rows (width);
+			int rows = m_outputLines[end].rows (width);
 
 			if (usedHeight + rows > height)
 			{
@@ -353,7 +354,7 @@
 	if (start > 0)
 		printOffset = height - usedHeight;
 
-	OutputScroll = OutputLines.size() - 1 - end;
+	m_outputScroll = m_outputLines.size() - 1 - end;
 
 	if (start < 0 or start == end or printOffset >= height)
 		return;
@@ -368,10 +369,10 @@
 	y += printOffset;
 
 	for (int i = start; i < end; ++i)
-		y = render_colorline (y, 0, width, OutputLines[i], true);
+		y = render_colorline (y, 0, width, m_outputLines[i], true);
 
-	NeedOutputRender = false;
-	NeedRefresh = true;
+	m_needOutputRender = false;
+	m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -390,9 +391,9 @@
 	{
 		mvhline (y, x, ' ', width);
 
-		if (i < PlayerNames.size())
+		if (i < m_playerNames.size())
 		{
-			String displaynick = PlayerNames[i];
+			String displaynick = m_playerNames[i];
 
 			if (displaynick.length() > width)
 			{
@@ -406,8 +407,8 @@
 		y++;
 	}
 
-	NeedNicklistRender = false;
-	NeedRefresh = true;
+	m_needNicklistRender = false;
+	m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -418,13 +419,13 @@
 
 	// If we're asking the user if they want to disconnect, we don't render any input strings,
 	// just the confirmation message.
-	if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		attron (promptColor);
 		mvhline (LINES - 2, 0, ' ', COLS);
 		mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n");
 		attroff (promptColor);
-		NeedRefresh = true;
+		m_needRefresh = true;
 		return;
 	}
 
@@ -434,25 +435,25 @@
 	int y = LINES - 2;
 
 	// If we're inputting a password, replace it with asterisks
-	if (CurrentInputState == INPUTSTATE_PASSWORD)
+	if (m_inputState == INPUTSTATE_PASSWORD)
 	{
 		for (int i = 0; i < displayString.length(); ++i)
 			displayString[i] = '*';
 	}
 
 	// Ensure the cursor is within bounds
-	CursorPosition = clamp (CursorPosition, 0, displayString.length());
+	m_cursorPosition = clamp (m_cursorPosition, 0, displayString.length());
 
 	// Ensure that the cursor is always in view, adjust panning if this is not the case
-	if (CursorPosition > InputPanning + displayLength)
-		InputPanning = CursorPosition - displayLength; // cursor went too far right
-	else if (CursorPosition < InputPanning)
-		InputPanning = CursorPosition; // cursor went past the pan value to the left
+	if (m_cursorPosition > m_inputPanning + displayLength)
+		m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right
+	else if (m_cursorPosition < m_inputPanning)
+		m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left
 
 	// What part of the string to draw?
-	int start = InputPanning;
+	int start = m_inputPanning;
 	int end = min<int> (displayString.length(), start + displayLength);
-	assert (CursorPosition >= start and CursorPosition <= end);
+	assert (m_cursorPosition >= start and m_cursorPosition <= end);
 
 	// Render the input string
 	mvhline (LINES - 2, 0, ' ', COLS);
@@ -465,10 +466,10 @@
 
 	// Store in memory where the cursor is now (so that we can re-draw it to position the terminal
 	// cursor).
-	CursorCharacter.ch = CursorPosition != 0 ? displayString[CursorPosition - 1] : '\0';
-	CursorCharacter.x = prompt.length() + (CursorPosition - InputPanning);
-	NeedRefresh = true;
-	NeedInputRender = false;
+	m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0';
+	m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning);
+	m_needRefresh = true;
+	m_needInputRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -479,10 +480,10 @@
 	int y = LINES - 1;
 	attron (color);
 	mvhline (y, 0, ' ', COLS);
-	mvprintw (y, 0, "%s", StatusBarText.chars());
+	mvprintw (y, 0, "%s", m_statusBarText.chars());
 	attroff (color);
-	NeedRefresh = true;
-	NeedStatusBarRender = false;
+	m_needRefresh = true;
+	m_needStatusBarRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -491,7 +492,7 @@
 {
 	String text;
 
-	switch (Session.state())
+	switch (m_session.state())
 	{
 	case RCON_DISCONNECTED:
 		text = "Disconnected.";
@@ -499,26 +500,26 @@
 
 	case RCON_CONNECTING:
 	case RCON_AUTHENTICATING:
-		text = "Connecting to " + Session.address().to_string (IPAddress::WITH_PORT) + "...";
+		text = "Connecting to " + m_session.address().to_string (IPAddress::WITH_PORT) + "...";
 		break;
 
 	case RCON_CONNECTED:
 		{
 			String adminText;
 
-			if (Session.num_admins() == 0)
+			if (m_session.num_admins() == 0)
 			{
 				adminText = "No other admins";
 			}
 			else
 			{
-				adminText.sprintf ("%d other admin%s", Session.num_admins(),
-					Session.num_admins() != 1 ? "s" : "");
+				adminText.sprintf ("%d other admin%s", m_session.num_admins(),
+					m_session.num_admins() != 1 ? "s" : "");
 			}
 
 			text.sprintf ("%s | %s | %s",
-				Session.address().to_string (IPAddress::WITH_PORT).chars(),
-				Session.level().chars(),
+				m_session.address().to_string (IPAddress::WITH_PORT).chars(),
+				m_session.level().chars(),
 				adminText.chars());
 		}
 		break;
@@ -527,12 +528,13 @@
 	if (not text.is_empty())
 		text += " | ";
 
-	text += "Ctrl+N to connect, Ctrl+Q to quit";
+	text += "Ctrl+N to connect, Ctrl+Q to ";
+	text += (m_session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect";
 
-	if (text != StatusBarText)
+	if (text != m_statusBarText)
 	{
-		StatusBarText = text;
-		NeedStatusBarRender = true;
+		m_statusBarText = text;
+		m_needStatusBarRender = true;
 	}
 }
 
@@ -553,13 +555,13 @@
 void Interface::position_cursor()
 {
 	// This is only relevant if the input string is being drawn
-	if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 		return;
 
 	int y = LINES - 2;
 
-	if (CursorCharacter.ch != '\0')
-		mvprintw (y, CursorCharacter.x, "%c", CursorCharacter.ch);
+	if (m_cursorCharacter.ch != '\0')
+		mvprintw (y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch);
 	else
 		mvprintw (y, prompt_string().length(), " ");
 }
@@ -569,7 +571,7 @@
 int Interface::find_previous_word()
 {
 	const String& input = current_input();
-	int pos = CursorPosition;
+	int pos = m_cursorPosition;
 
 	// Move past whitespace
 	while (pos > 0 and isspace (input[pos - 1]))
@@ -587,7 +589,7 @@
 int Interface::find_next_word()
 {
 	const String& input = current_input();
-	int pos = CursorPosition;
+	int pos = m_cursorPosition;
 
 	// Move past current whitespace
 	while (pos < input.length() and isspace (input[pos]))
@@ -607,13 +609,13 @@
 	if (a >= b)
 		return;
 
-	if (CursorPosition > a and CursorPosition <= b)
-		CursorPosition = a;
+	if (m_cursorPosition > a and m_cursorPosition <= b)
+		m_cursorPosition = a;
 
 	String& input = mutable_current_input();
-	PasteBuffer = input.mid (a, b);
+	m_pasteBuffer = input.mid (a, b);
 	input.remove (a, b - a);
-	NeedInputRender = true;
+	m_needInputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -632,7 +634,7 @@
 		return;
 	}
 
-	if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		if (ch == 'y' or ch == 'Y')
 			disconnected();
@@ -644,23 +646,22 @@
 
 	if (ch >= 0x20 and ch <= 0x7E)
 	{
-		mutable_current_input().insert (CursorPosition++, char (ch));
-		NeedInputRender = true;
+		mutable_current_input().insert (m_cursorPosition++, char (ch));
+		m_needInputRender = true;
 	}
 	else switch (ch)
 	{
 	case 'Q' - 'A' + 1: // ^Q
-		switch (CurrentInputState)
+		switch (m_inputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break;
 
 		case INPUTSTATE_NORMAL:
-			safe_disconnect ([&]()
+			safe_disconnect ([&](bool hadsession)
 			{
-				if (Session.is_active())
+				if (hadsession)
 				{
-					Session.disconnect();
 					set_input_state (INPUTSTATE_NORMAL);
 				}
 				else
@@ -682,19 +683,19 @@
 
 	case KEY_LEFT:
 	case 'B' - 'A' + 1: // readline ^B
-		if (CursorPosition > 0)
+		if (m_cursorPosition > 0)
 		{
-			CursorPosition--;
-			NeedInputRender = true;
+			m_cursorPosition--;
+			m_needInputRender = true;
 		}
 		break;
 
 	case KEY_RIGHT:
 	case 'F' - 'A' + 1: // readline ^F
-		if (CursorPosition < current_input().length())
+		if (m_cursorPosition < current_input().length())
 		{
-			CursorPosition++;
-			NeedInputRender = true;
+			m_cursorPosition++;
+			m_needInputRender = true;
 		}
 		break;
 
@@ -705,72 +706,72 @@
 
 	case KEY_HOME:
 	case 'A' - 'A' + 1: // readline ^A
-		if (CursorPosition != 0)
+		if (m_cursorPosition != 0)
 		{
-			CursorPosition = 0;
-			NeedInputRender = true;
+			m_cursorPosition = 0;
+			m_needInputRender = true;
 		}
 		break;
 
 	case KEY_END:
 	case 'E' - 'A' + 1: // readline ^E
-		if (CursorPosition != current_input().length())
+		if (m_cursorPosition != current_input().length())
 		{
-			CursorPosition = current_input().length();
-			NeedInputRender = true;
+			m_cursorPosition = current_input().length();
+			m_needInputRender = true;
 		}
 		break;
 
 	case KEY_BACKSPACE:
 	case '\b':
-		if (CursorPosition > 0)
+		if (m_cursorPosition > 0)
 		{
-			mutable_current_input().remove_at (--CursorPosition);
-			NeedInputRender = true;
+			mutable_current_input().remove_at (--m_cursorPosition);
+			m_needInputRender = true;
 		}
 		break;
 
 	case KEY_DC:
 	case 'D' - 'A' + 1: // readline ^D
-		if (CursorPosition < current_input().length())
+		if (m_cursorPosition < current_input().length())
 		{
-			mutable_current_input().remove_at (CursorPosition);
-			NeedInputRender = true;
+			mutable_current_input().remove_at (m_cursorPosition);
+			m_needInputRender = true;
 		}
 		break;
 
 	case KEY_PPAGE:
-		OutputScroll += min (g_pageSize, LINES / 2);
-		NeedOutputRender = true;
+		m_outputScroll += min (PAGE_SIZE, LINES / 2);
+		m_needOutputRender = true;
 		break;
 
 	case KEY_NPAGE:
-		OutputScroll -= min (g_pageSize, LINES / 2);
-		NeedOutputRender = true;
+		m_outputScroll -= min (PAGE_SIZE, LINES / 2);
+		m_needOutputRender = true;
 		break;
 
 	case 'U' - 'A' + 1: // readline ^U - delete from start to cursor
-		if (CursorPosition > 0)
+		if (m_cursorPosition > 0)
 		{
-			yank (0, CursorPosition);
-			CursorPosition = 0;
+			yank (0, m_cursorPosition);
+			m_cursorPosition = 0;
 		}
 		break;
 
 	case 'K' - 'A' + 1: // readline ^K - delete from cursor to end
-		yank (CursorPosition, mutable_current_input().length());
+		yank (m_cursorPosition, mutable_current_input().length());
 		break;
 
 	case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current
-		yank (find_previous_word(), CursorPosition);
+		yank (find_previous_word(), m_cursorPosition);
 		break;
 
 	case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text
-		if (not PasteBuffer.is_empty())
+		if (not m_pasteBuffer.is_empty())
 		{
-			mutable_current_input().insert (CursorPosition, PasteBuffer);
-			CursorPosition += PasteBuffer.length();
-			NeedInputRender = true;
+			mutable_current_input().insert (m_cursorPosition, m_pasteBuffer);
+			m_cursorPosition += m_pasteBuffer.length();
+			m_needInputRender = true;
 		}
 		break;
 
@@ -778,12 +779,12 @@
 		{
 			int space = current_input().find (" ");
 
-			if (CurrentInputState == INPUTSTATE_NORMAL
-				and CursorPosition > 0
-				and (space == -1 or space >= CursorPosition))
+			if (m_inputState == INPUTSTATE_NORMAL
+				and m_cursorPosition > 0
+				and (space == -1 or space >= m_cursorPosition))
 			{
-				String start = current_input().mid (0, CursorPosition);
-				Session.request_tab_complete (start);
+				String start = current_input().mid (0, m_cursorPosition);
+				m_session.request_tab_complete (start);
 			}
 		}
 		break;
@@ -791,7 +792,7 @@
 	case '\n':
 	case '\r':
 	case KEY_ENTER:
-		switch (CurrentInputState)
+		switch (m_inputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break; // handled above
@@ -799,7 +800,7 @@
 		case INPUTSTATE_ADDRESS:
 			try
 			{
-				CurrentAddress = IPAddress::from_string (current_input());
+				m_remoteAddress = IPAddress::from_string (current_input());
 			}
 			catch (std::exception& e)
 			{
@@ -807,18 +808,18 @@
 				return;
 			}
 
-			if (CurrentAddress.port == 0)
-				CurrentAddress.port = 10666;
+			if (m_remoteAddress.port == 0)
+				m_remoteAddress.port = 10666;
 
 			set_input_state (INPUTSTATE_PASSWORD);
 			break;
 
 		case INPUTSTATE_PASSWORD:
-			if (CurrentInputState == INPUTSTATE_PASSWORD and not current_input().is_empty())
+			if (m_inputState == INPUTSTATE_PASSWORD and not current_input().is_empty())
 			{
-				Session.disconnect();
-				Session.set_password (current_input());
-				Session.connect (CurrentAddress);
+				m_session.disconnect();
+				m_session.set_password (current_input());
+				m_session.connect (m_remoteAddress);
 				set_input_state (INPUTSTATE_NORMAL);
 			}
 			break;
@@ -827,21 +828,19 @@
 			if (current_input()[0] == '/')
 			{
 				handle_command(current_input());
-				InputHistory.insert (0, "");
-				NeedInputRender = true;
+				flush_input();
 			}
-			else if (Session.send_command (current_input()))
+			else if (m_session.send_command (current_input()))
 			{
-				InputHistory.insert (0, "");
-				NeedInputRender = true;
+				flush_input();
 			}
 			break;
 		}
 		break;
 
 	case 'N' - 'A' + 1: // ^N
-		if (CurrentInputState == INPUTSTATE_NORMAL)
-			safe_disconnect ([&]() {set_input_state (INPUTSTATE_ADDRESS);});
+		if (m_inputState == INPUTSTATE_NORMAL)
+			safe_disconnect ([&](bool){set_input_state (INPUTSTATE_ADDRESS);});
 		break;
 
 	case '\x1b': // Escape
@@ -855,30 +854,35 @@
 			case 'b':
 			case 'B':
 				// readline alt-b - move one word to the left
-				CursorPosition = find_previous_word();
-				NeedInputRender = true;
+				m_cursorPosition = find_previous_word();
+				m_needInputRender = true;
 				break;
 
 			case 'f':
 			case 'F':
 				// readline alt-f - move one word to the right
-				CursorPosition = find_next_word();
-				NeedInputRender = true;
+				m_cursorPosition = find_next_word();
+				m_needInputRender = true;
 				break;
 
 			case 'd':
 			case 'D':
 				// readline alt-d - delete from here till next word boundary
-				yank (CursorPosition, find_next_word());
+				yank (m_cursorPosition, find_next_word());
+				break;
+
+			case KEY_BACKSPACE: // alt+backspace, remove previous word
+			case '\b':
+				yank (find_previous_word(), m_cursorPosition);
 				break;
 			}
 		}
 		else
 		{
 			// No alt-key, handle pure escape
-			if (CurrentInputState == INPUTSTATE_PASSWORD)
+			if (m_inputState == INPUTSTATE_PASSWORD)
 				set_input_state (INPUTSTATE_ADDRESS);
-			else if (CurrentInputState == INPUTSTATE_ADDRESS)
+			else if (m_inputState == INPUTSTATE_ADDRESS)
 				set_input_state (INPUTSTATE_NORMAL);
 		}
 		break;
@@ -891,16 +895,16 @@
 //
 void Interface::render()
 {
-	if (NeedStatusBarRender) render_statusbar();
-	if (NeedInputRender) render_input();
-	if (NeedOutputRender) render_output();
-	if (NeedNicklistRender) render_nicklist();
+	if (m_needStatusBarRender) render_statusbar();
+	if (m_needInputRender) render_input();
+	if (m_needOutputRender) render_output();
+	if (m_needNicklistRender) render_nicklist();
 
-	if (NeedRefresh)
+	if (m_needRefresh)
 	{
 		position_cursor();
 		refresh();
-		NeedRefresh = false;
+		m_needRefresh = false;
 	}
 }
 
@@ -915,10 +919,21 @@
 
 // -------------------------------------------------------------------------------------------------
 //
+void __cdecl Interface::print_text (const char* fmtstr, ...)
+{
+	va_list args;
+	va_start (args, fmtstr);
+	vprint (fmtstr, args);
+	va_end (args);
+}
+
+// -------------------------------------------------------------------------------------------------
+//
 void __cdecl Interface::print (const char* fmtstr, ...)
 {
 	va_list args;
 	va_start (args, fmtstr);
+	print_to_console (TEXTCOLOR_BrightBlue);
 	vprint (fmtstr, args);
 	va_end (args);
 }
@@ -931,7 +946,6 @@
 	va_start (args, fmtstr);
 	print_to_console (TEXTCOLOR_BrightYellow "-!- ");
 	vprint (fmtstr, args);
-	print_to_console (TEXTCOLOR_Reset);
 	va_end (args);
 }
 
@@ -943,7 +957,6 @@
 	va_start (args, fmtstr);
 	print_to_console (TEXTCOLOR_BrightRed "!!! ");
 	vprint (fmtstr, args);
-	print_to_console (TEXTCOLOR_Reset);
 	va_end (args);
 }
 
@@ -961,12 +974,12 @@
 
 		if (ch == '\n')
 		{
-			OutputLines.last().finalize();
-			OutputLines << ColoredLine();
+			m_outputLines.last().finalize();
+			m_outputLines << ColoredLine();
 			continue;
 		}
 
-		if (OutputLines.last().length() == 0)
+		if (m_outputLines.last().length() == 0)
 		{
 			time_t now;
 			time (&now);
@@ -974,13 +987,17 @@
 			strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now));
 
 			for (char* cp = timestamp; *cp != '\0'; ++cp)
-				OutputLines.last().add_char (*cp);
+				m_outputLines.last().add_char (*cp);
 		}
 
-		OutputLines.last().add_char (ch);
+		// Remove some lines if there's too many of them. 20,000 should be enough, I hope.
+		while (m_outputLines.size() > 20000)
+			m_outputLines.remove_at(0);
+
+		m_outputLines.last().add_char (ch);
 	}
 
-	NeedOutputRender = true;
+	m_needOutputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -989,7 +1006,7 @@
 {
 	try
 	{
-		CurrentAddress = IPAddress::from_string (address);
+		m_remoteAddress = IPAddress::from_string (address);
 	}
 	catch (std::exception& e)
 	{
@@ -997,20 +1014,20 @@
 		return;
 	}
 
-	if (CurrentAddress.port == 0)
-		CurrentAddress.port = 10666;
+	if (m_remoteAddress.port == 0)
+		m_remoteAddress.port = 10666;
 
-	Session.disconnect();
-	Session.set_password (password);
-	Session.connect (CurrentAddress);
+	m_session.disconnect();
+	m_session.set_password (password);
+	m_session.connect (m_remoteAddress);
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::set_player_names (const StringList& names)
 {
-	PlayerNames = names;
-	NeedNicklistRender = true;
+	m_playerNames = names;
+	m_needNicklistRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -1025,21 +1042,13 @@
 			complete += ' ';
 
 		input.replace (0, part.length(), complete);
-		CursorPosition = complete.length();
-		NeedInputRender = true;
+		m_cursorPosition = complete.length();
+		m_needInputRender = true;
 	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::disconnected()
-{
-	Session.disconnect();
-	set_input_state (INPUTSTATE_NORMAL);
-}
-
-// -------------------------------------------------------------------------------------------------
-//
 void Interface::handle_command(const String& input)
 {
 	if (input[0] != '/')
@@ -1052,7 +1061,9 @@
 	if (command == "connect")
 	{
 		if (args.size() != 2)
-			print_error("Usage: /connect <address> <password>");
+		{
+			print_error("Usage: /connect <address> <password>\n");
+		}
 		else
 		{
 			IPAddress address;
@@ -1070,21 +1081,25 @@
 			if (address.port == 0)
 				address.port = 10666;
 
-			Session.set_password(args[1]);
-			Session.disconnect();
-			Session.connect(CurrentAddress = address);
+			m_session.set_password(args[1]);
+			m_session.disconnect();
+			m_session.connect(m_remoteAddress = address);
 		}
 	}
+	else if (command == "disconnect")
+	{
+		m_session.disconnect();
+	}
 	else if (command == "quit")
 	{
-		Session.disconnect();
+		m_session.disconnect();
 		endwin();
 		throw Exitception();
 	}
 	else if (command == "watch")
 	{
 		if (not args.is_empty())
-			Session.request_watch(args);
+			m_session.request_watch(args);
 		else
 			print_error("No CVars to watch.\n");
 	}
@@ -1092,4 +1107,29 @@
 		print_error("Unknown command %s\n", command.chars());
 }
 
+// -------------------------------------------------------------------------------------------------
+//
+void Interface::disconnected()
+{
+	print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars());
+	reset_title();
+	render_full();
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+void Interface::reset_title()
+{
+	m_title.sprintf ("%s %s (%s)", application_name(), full_version_string(), changeset_date_string());
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+void Interface::flush_input()
+{
+	m_inputHistory.insert (0, "");
+	m_inputCursor = 0;
+	m_needInputRender = true;
+}
+
 END_ZFC_NAMESPACE
--- a/sources/interface.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/interface.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -58,39 +58,40 @@
 	void set_player_names (const StringList& names);
 	void need_refresh();
 	void tab_complete (const String& part, String complete);
-	RCONSession* get_session() { return &Session; }
+	RCONSession* get_session() { return &m_session; }
+	void handle_command(const String& input);
 	void disconnected();
-	void handle_command(const String& input);
-
+	
 	void vprint (const char* fmtstr, va_list args);
 	void __cdecl print (const char* fmtstr, ...);
 	void __cdecl print_warning (const char* fmtstr, ...);
 	void __cdecl print_error (const char* fmtstr, ...);
+	void __cdecl print_text (const char* fmtstr, ...);
 
 private:
-	StringList InputHistory;
-	int InputCursor;
-	int CursorPosition;
-	int InputPanning;
-	bool NeedRefresh;
-	bool NeedStatusBarRender;
-	bool NeedInputRender;
-	bool NeedOutputRender;
-	bool NeedNicklistRender;
-	struct { char ch; int x; } CursorCharacter;
-	Vector<ColoredLine> OutputLines;
-	int OutputScroll;
-	String Title;
-	InputState CurrentInputState;
-	std::function<void (void)> DisconnectConfirmFunction;
-	IPAddress CurrentAddress;
-	String StatusBarText;
-	StringList PlayerNames;
-	String PasteBuffer;
-	RCONSession Session;
+	StringList m_inputHistory;
+	int m_inputCursor;
+	int m_cursorPosition;
+	int m_inputPanning;
+	bool m_needRefresh;
+	bool m_needStatusBarRender;
+	bool m_needInputRender;
+	bool m_needOutputRender;
+	bool m_needNicklistRender;
+	struct { char ch; int x; } m_cursorCharacter;
+	Vector<ColoredLine> m_outputLines;
+	int m_outputScroll;
+	String m_title;
+	InputState m_inputState;
+	std::function<void(bool)> m_disconnectCallback;
+	IPAddress m_remoteAddress;
+	String m_statusBarText;
+	StringList m_playerNames;
+	String m_pasteBuffer;
+	RCONSession m_session;
 
 	void render_titlebar();
-	void safe_disconnect (std::function<void()> afterwards);
+	void safe_disconnect (std::function<void(bool)> afterwards);
 	int render_colorline (int y, int x0, int width, const ColoredLine& line, bool allowWrap);
 	int nicklist_width();
 	void render_output();
@@ -109,6 +110,8 @@
 	void yank (int a, int b);
 	int find_previous_word();
 	int find_next_word();
+	void reset_title();
+	void flush_input();
 };
 
-END_ZFC_NAMESPACE
+END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/list.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/list.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/main.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/main.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/main.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/main.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/mystring.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/mystring.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -54,7 +54,19 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-String String::strip (const List<char>& unwanted)
+String String::strip (char unwanted) const
+{
+	String result (m_string);
+
+	for (int pos = 0; (pos = result.find (unwanted)) != -1;)
+		result.remove_at (pos--);
+
+	return result;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+String String::strip (const List<char>& unwanted) const
 {
 	String result (m_string);
 
@@ -161,23 +173,20 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-String String::mid (long a, long b) const
+// Returns a substring from [a, b)
+//
+String String::mid (int a, int b) const
 {
-	if (b == -1 or b > length())
+	a = max(a, 0);
+	b = min(b, length());
+
+	if (b == -1)
 		b = length();
 
 	if (b <= a)
 		return "";
-
-	if (a == 0 and b == length())
-		return *this;
-
-	char* newstr = new char[b - a + 1];
-	strncpy (newstr, chars() + a, b - a);
-	newstr[b - a] = '\0';
-	String other (newstr);
-	delete[] newstr;
-	return other;
+	else
+		return m_string.substr(a, b - a);
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -221,6 +230,18 @@
 
 // -------------------------------------------------------------------------------------------------
 //
+int String::find (char ch, int a) const
+{
+	int pos = m_string.find (ch, a);
+
+	if (pos == int (std::string::npos))
+		return -1;
+
+	return pos;
+}
+
+// -------------------------------------------------------------------------------------------------
+//
 int String::find_last (const char* c, int a) const
 {
 	if (a == -1 or a >= length())
--- a/sources/mystring.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/mystring.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -69,18 +69,21 @@
 	int count (char needle) const;
 	const char* chars() const { return m_string.c_str(); }
 	ConstIterator end() const { return m_string.end(); }
-	int find (const char*c, int a = 0) const;
+	int find (const char* c, int a = 0) const;
+	int find (char ch, int a = 0) const;
 	bool is_empty() const { return m_string[0] == '\0'; }
 	bool is_numeric() const;
 	int find_last (const char*c, int a) const;
 	int length() const { return m_string.length(); }
 	bool mask_against (const String &pattern) const;
 	String md5() const;
-	String mid (long a, long b) const;
+	String mid (int a, int b) const;
 	String right (int length) const;
 	StringList split (const String &del) const;
 	StringList split (char del) const;
 	const std::string& std_string() const { return m_string; }
+	String strip (char unwanted) const;
+	String strip (const List<char> &unwanted) const;
 	double to_double (bool* ok = nullptr) const;
 	float to_float (bool* ok = nullptr) const;
 	long to_int (bool* ok = nullptr, int base = 10) const;
@@ -112,8 +115,6 @@
 	void __cdecl sprintf (const char* fmtstr, ...);
 	void vsprintf (const char* fmtstr, va_list args);
 	bool starts_with (const String &other) const;
-	String strip (char unwanted) { return strip ({unwanted}); }
-	String strip (const List<char> &unwanted);
 	void trim (int n);
 
 	static String from_number (short int a);
--- a/sources/network/bytestream.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/bytestream.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/network/bytestream.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/bytestream.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/network/ipaddress.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/ipaddress.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/network/ipaddress.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/ipaddress.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/network/rconsession.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/rconsession.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -73,8 +73,7 @@
 		Bytestream packet;
 		packet.write_byte (CLRC_DISCONNECT);
 		this->send (packet);
-		m_interface->print ("Disconnected from %s\n", m_address.to_string (IPAddress::WITH_PORT).chars());
-		m_interface->update_statusbar();
+		m_interface->disconnected();
 	}
 
 	m_state = RCON_DISCONNECTED;
@@ -165,7 +164,7 @@
 				{
 					String message = packet.read_string();
 					message.normalize();
-					m_interface->print ("%s\n", message.chars());
+					m_interface->print_text ("%s\n", message.chars());
 				}
 				break;
 
@@ -185,7 +184,7 @@
 				{
 					String message = packet.read_string();
 					message.normalize();
-					m_interface->print ("--- %s\n", message.chars());
+					m_interface->print_text ("--- %s\n", message.chars());
 				}
 
 				m_interface->print ("End of previous messages.\n");
--- a/sources/network/rconsession.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/rconsession.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/network/udpsocket.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/udpsocket.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/network/udpsocket.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/network/udpsocket.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/range.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/range.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/version.cpp	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/version.cpp	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -35,9 +35,39 @@
 
 #include <stdio.h>
 #include <string.h>
+#include "basics.h"
 #include "version.h"
 #include "hginfo.h"
 
+#define VERSION_MAJOR 1
+#define VERSION_MINOR 2
+#define VERSION_PATCH 0
+
+#if VERSION_PATCH == 0
+# define VERSION_STRING \
+	MACRO_TO_STRING (VERSION_MAJOR) \
+	"." MACRO_TO_STRING (VERSION_MINOR)
+#else
+# define VERSION_STRING \
+	MACRO_TO_STRING (VERSION_MAJOR) \
+	"." MACRO_TO_STRING (VERSION_MINOR) \
+	"." MACRO_TO_STRING (VERSION_PATCH)
+#endif
+
+// -------------------------------------------------------------------------------------------------
+//
+const char* application_name()
+{
+	return "ZFC9000";
+}
+
+// -------------------------------------------------------------------------------------------------
+//
+const char* version_string()
+{
+	return VERSION_STRING;
+}
+
 // -------------------------------------------------------------------------------------------------
 //
 const char* full_version_string()
--- a/sources/version.h	Mon Jan 25 04:15:31 2016 +0200
+++ b/sources/version.h	Wed Jul 20 12:55:39 2016 +0300
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014, 2015 Teemu Piippo
+	Copyright 2014 - 2016 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -29,37 +29,15 @@
 */
 
 #pragma once
-#include "basics.h"
-#define APPNAME "zfc9000"
-#define VERSION_MAJOR 1
-#define VERSION_MINOR 1
-#define VERSION_PATCH 0
-// #define IS_RELEASE
-
-// -------------------------------------------------------------------------------------------------
-// Version string
 
-#if VERSION_PATCH == 0
-# define VERSION_STRING \
-	MACRO_TO_STRING (VERSION_MAJOR) \
-	"." MACRO_TO_STRING (VERSION_MINOR)
-#else
-# define VERSION_STRING \
-	MACRO_TO_STRING (VERSION_MAJOR) \
-	"." MACRO_TO_STRING (VERSION_MINOR) \
-	"." MACRO_TO_STRING (VERSION_PATCH)
-#endif
+// Returns the application name (ZFC9000)
+const char* application_name();
 
-// -------------------------------------------------------------------------------------------------
 // Returns the bare version string (1.2.3)
-
-inline const char* version_string()
-{
-	return VERSION_STRING;
-}
+const char* version_string();
 
 // Returns full version string, with hash (1.2.3-abcd456)
 const char* full_version_string();
 
 // Returns changeset date string
-const char* changeset_date_string();
\ No newline at end of file
+const char* changeset_date_string();
--- a/updaterevision/updaterevision.py	Mon Jan 25 04:15:31 2016 +0200
+++ b/updaterevision/updaterevision.py	Wed Jul 20 12:55:39 2016 +0300
@@ -36,11 +36,11 @@
 	print 'usage: %s <output>' % sys.argv[0]
 	quit (1)
 
-oldrev = ''
+oldcompare = ''
 
 try:
 	with open (sys.argv[1]) as fp:
-		oldrev = fp.readline().replace ('\n', '').replace ('// ', '')
+		oldcompare = fp.readline().replace ('\n', '').replace ('// ', '')
 except IOError:
     pass
 
@@ -52,25 +52,32 @@
 date = datetime.utcfromtimestamp (timestamp)
 datestring = date.strftime ('%y%m%d-%H%M') if date.year >= 2000 else '000000-0000'
 
+if tags and tags != 'tip':
+	tag = tags.split(' ')[0]
+else:
+	tag = None
+
 if len(rev) > 7:
 	rev = rev[0:7]
 
 if subprocess.check_output (['hg', 'id', '-n']).replace ('\n', '')[-1] == '+':
 	rev += '+'
 
-if rev == oldrev:
-	print "%s is up to date at %s" % (sys.argv[1], rev)
+compare = tag or rev
+
+if compare == oldcompare:
+	print "%s is up to date at %s" % (sys.argv[1], compare)
 	quit (0)
 
 with open (sys.argv[1], 'w') as fp:
-	fp.write ('// %s\n' % rev)
+	fp.write ('// %s\n' % compare)
 	fp.write ('#define HG_NODE "%s"\n' % rev)
 	fp.write ('#define HG_BRANCH "%s"\n' % branch)
 	fp.write ('#define HG_DATE_VERSION "%s"\n' % datestring)
 	fp.write ('#define HG_DATE_STRING "%s"\n' % date.strftime ('%d %b %Y'))
 	fp.write ('#define HG_DATE_TIME %d\n' % int (timestamp))
 
-	if tags and tags != 'tip':
+	if tag:
 		fp.write ('#define HG_TAG "%s"\n' % tags.split(' ')[0])
 
-	print '%s updated to %s' % (sys.argv[1], rev)
+	print '%s updated to %s' % (sys.argv[1], compare)

mercurial