sources/interface.cpp

branch
protocol5
changeset 106
7b156b764d11
parent 103
b78c0ca832a9
parent 105
b4466472aecd
child 131
4996c8684b93
equal deleted inserted replaced
104:a76af67a3a4b 106:7b156b764d11
26 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */ 29 */
30 30
31 #include <curses.h>
31 #include <string.h> 32 #include <string.h>
32 #include <time.h> 33 #include <time.h>
33 #include "interface.h" 34 #include "interface.h"
34 #include "network/rconsession.h" 35 #include "network/rconsession.h"
35 #include "network/ipaddress.h" 36 #include "network/ipaddress.h"
36 #include "coloredline.h" 37 #include "coloredline.h"
38 BEGIN_ZFC_NAMESPACE
37 39
38 static const int g_pageSize = 10; 40 static const int g_pageSize = 10;
39 41
40 // ------------------------------------------------------------------------------------------------- 42 // -------------------------------------------------------------------------------------------------
41 // 43 //
42 int Interface::color_pair (Color fg, Color bg) 44 chtype Interface::color_pair (Color fg, Color bg)
43 { 45 {
44 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg)); 46 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg));
45 } 47 }
46 48
47 // ------------------------------------------------------------------------------------------------- 49 // -------------------------------------------------------------------------------------------------
125 127
126 switch (newstate) 128 switch (newstate)
127 { 129 {
128 case INPUTSTATE_ADDRESS: 130 case INPUTSTATE_ADDRESS:
129 if (CurrentAddress.host != 0) 131 if (CurrentAddress.host != 0)
130 mutable_current_input() = CurrentAddress.to_string (IP_WITH_PORT); 132 mutable_current_input() = CurrentAddress.to_string (IPAddress::WITH_PORT);
131 break; 133 break;
132 134
133 default: 135 default:
134 break; 136 break;
135 } 137 }
139 } 141 }
140 142
141 // ------------------------------------------------------------------------------------------------- 143 // -------------------------------------------------------------------------------------------------
142 // 144 //
143 Interface::Interface() : 145 Interface::Interface() :
144 Session (this) 146 InputCursor (0),
145 { 147 CursorPosition (0),
146 #ifdef XCURSES 148 InputPanning (0),
147 ::Xinitscr(argc, argv); 149 NeedRefresh (false),
148 #else 150 NeedStatusBarRender (false),
149 ::initscr(); 151 NeedInputRender (false),
150 #endif 152 NeedOutputRender (false),
151 153 NeedNicklistRender (false),
152 ::cbreak(); 154 OutputScroll (0),
155 CurrentInputState (INPUTSTATE_NORMAL),
156 DisconnectConfirmFunction (nullptr)
157 {
158 ::initscr();
159 ::raw();
153 ::keypad (stdscr, true); 160 ::keypad (stdscr, true);
154 ::noecho(); 161 ::noecho();
155 ::refresh(); 162 ::refresh();
156 ::timeout (0); 163 ::timeout (0);
157 InputHistory.clear(); 164 InputHistory.clear();
158 InputHistory << ""; 165 InputHistory << "";
159 OutputLines.clear(); 166 OutputLines.clear();
160 OutputLines << ColoredLine(); 167 OutputLines << ColoredLine();
161 Title.sprintf (APPNAME " %s (%d)", full_version_string(), changeset_date_string()); 168 Session.set_interface (this);
169 Title.sprintf (APPNAME " %s (%s)", full_version_string(), changeset_date_string());
162 170
163 if (::has_colors()) 171 if (::has_colors())
164 { 172 {
165 ::start_color(); 173 ::start_color();
166 ::use_default_colors(); 174 ::use_default_colors();
194 // 202 //
195 void Interface::render_titlebar() 203 void Interface::render_titlebar()
196 { 204 {
197 if (Title.length() <= COLS) 205 if (Title.length() <= COLS)
198 { 206 {
199 int pair = color_pair (WHITE, BLUE); 207 chtype pair = color_pair (WHITE, BLUE);
200 int startx = (COLS - Title.length()) / 2; 208 int startx = (COLS - Title.length()) / 2;
201 int endx = startx + Title.length(); 209 int endx = startx + Title.length();
202 attron (pair); 210 attron (pair);
203 mvprintw (0, startx, "%s", Title.chars()); 211 mvprintw (0, startx, "%s", Title.chars());
204 mvhline (0, 0, ' ', startx); 212 mvhline (0, 0, ' ', startx);
217 render_titlebar(); 225 render_titlebar();
218 } 226 }
219 227
220 // ------------------------------------------------------------------------------------------------- 228 // -------------------------------------------------------------------------------------------------
221 // 229 //
222 void Interface::safe_disconnect (Function<void()> afterwards) 230 void Interface::safe_disconnect (std::function<void()> afterwards)
223 { 231 {
224 if (Session.is_active()) 232 if (Session.is_active())
225 { 233 {
226 DisconnectConfirmFunction = afterwards; 234 DisconnectConfirmFunction = afterwards;
227 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); 235 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION);
250 // 258 //
251 int Interface::render_colorline (int y, int x0, int width, const ColoredLine& line, bool allowWrap) 259 int Interface::render_colorline (int y, int x0, int width, const ColoredLine& line, bool allowWrap)
252 { 260 {
253 int x = x0; 261 int x = x0;
254 262
255 for (int byte : line.data()) 263 for (int i = 0; i < line.data().size(); ++i)
256 { 264 {
265 int byte = line.data()[i];
266
257 if (x == x0 + width) 267 if (x == x0 + width)
258 { 268 {
259 if (not allowWrap) 269 if (not allowWrap)
260 return y; 270 return y;
261 271
262 x = x0; 272 x = x0;
263 ++y; 273 ++y;
264 } 274 }
265 275
266 if (isprint (byte)) 276 if (byte < 256 && isprint (byte))
267 { 277 {
268 mvaddch (y, x, char (byte)); 278 mvaddch (y, x, char (byte));
269 ++x; 279 ++x;
270 } 280 }
281 else if (byte >= RLINE_ON_COLOR and byte < (RLINE_ON_COLOR + 16))
282 {
283 auto attrfunction = (byte < RLINE_OFF_COLOR ? &attron : &attroff);
284 (*attrfunction) (color_pair (Color ((byte - RLINE_ON_COLOR) & 7), DEFAULT));
285 }
271 else switch (byte) 286 else switch (byte)
272 { 287 {
273 case RLINE_ON_BLACK:
274 case RLINE_ON_RED:
275 case RLINE_ON_GREEN:
276 case RLINE_ON_YELLOW:
277 case RLINE_ON_BLUE:
278 case RLINE_ON_MAGENTA:
279 case RLINE_ON_CYAN:
280 case RLINE_ON_WHITE:
281 attron (color_pair (Color (byte - RLINE_ON_BLACK), DEFAULT));
282 break;
283
284 case RLINE_OFF_BLACK:
285 case RLINE_OFF_RED:
286 case RLINE_OFF_GREEN:
287 case RLINE_OFF_YELLOW:
288 case RLINE_OFF_BLUE:
289 case RLINE_OFF_MAGENTA:
290 case RLINE_OFF_CYAN:
291 case RLINE_OFF_WHITE:
292 attroff (color_pair (Color (byte - RLINE_OFF_BLACK), DEFAULT));
293 break;
294
295 case RLINE_ON_BOLD: 288 case RLINE_ON_BOLD:
296 attron (A_BOLD); 289 attron (A_BOLD);
297 break; 290 break;
298 291
299 case RLINE_OFF_BOLD: 292 case RLINE_OFF_BOLD:
419 412
420 // ------------------------------------------------------------------------------------------------- 413 // -------------------------------------------------------------------------------------------------
421 // 414 //
422 void Interface::render_input() 415 void Interface::render_input()
423 { 416 {
424 int promptColor = color_pair (WHITE, BLUE); 417 chtype promptColor = color_pair (WHITE, BLUE);
425 418
426 // If we're asking the user if they want to disconnect, we don't render any input strings, 419 // If we're asking the user if they want to disconnect, we don't render any input strings,
427 // just the confirmation message. 420 // just the confirmation message.
428 if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION) 421 if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION)
429 { 422 {
441 int y = LINES - 2; 434 int y = LINES - 2;
442 435
443 // If we're inputting a password, replace it with asterisks 436 // If we're inputting a password, replace it with asterisks
444 if (CurrentInputState == INPUTSTATE_PASSWORD) 437 if (CurrentInputState == INPUTSTATE_PASSWORD)
445 { 438 {
446 for (char& ch : displayString) 439 for (int i = 0; i < displayString.length(); ++i)
447 ch = '*'; 440 displayString[i] = '*';
448 } 441 }
449 442
450 // Ensure the cursor is within bounds 443 // Ensure the cursor is within bounds
451 CursorPosition = clamp (CursorPosition, 0, displayString.length()); 444 CursorPosition = clamp (CursorPosition, 0, displayString.length());
452 445
480 473
481 // ------------------------------------------------------------------------------------------------- 474 // -------------------------------------------------------------------------------------------------
482 // 475 //
483 void Interface::render_statusbar() 476 void Interface::render_statusbar()
484 { 477 {
485 int color = color_pair (WHITE, BLUE); 478 chtype color = color_pair (WHITE, BLUE);
486 int y = LINES - 1; 479 int y = LINES - 1;
487 attron (color); 480 attron (color);
488 mvhline (y, 0, ' ', COLS); 481 mvhline (y, 0, ' ', COLS);
489 mvprintw (y, 0, "%s", StatusBarText.chars()); 482 mvprintw (y, 0, "%s", StatusBarText.chars());
490 attroff (color); 483 attroff (color);
504 text = "Disconnected."; 497 text = "Disconnected.";
505 break; 498 break;
506 499
507 case RCON_CONNECTING: 500 case RCON_CONNECTING:
508 case RCON_AUTHENTICATING: 501 case RCON_AUTHENTICATING:
509 text = "Connecting to " + Session.address().to_string (IP_WITH_PORT) + "..."; 502 text = "Connecting to " + Session.address().to_string (IPAddress::WITH_PORT) + "...";
510 break; 503 break;
511 504
512 case RCON_CONNECTED: 505 case RCON_CONNECTED:
513 { 506 {
514 String adminText; 507 String adminText;
521 { 514 {
522 adminText.sprintf ("%d other admin%s", Session.num_admins(), 515 adminText.sprintf ("%d other admin%s", Session.num_admins(),
523 Session.num_admins() != 1 ? "s" : ""); 516 Session.num_admins() != 1 ? "s" : "");
524 } 517 }
525 518
526 text.sprintf ("%s | %s | %s", Session.address().to_string (IP_WITH_PORT).chars(), 519 text.sprintf ("%s | %s | %s",
527 Session.level().chars(), adminText.chars()); 520 Session.address().to_string (IPAddress::WITH_PORT).chars(),
521 Session.level().chars(),
522 adminText.chars());
528 } 523 }
529 break; 524 break;
530 } 525 }
531 526
532 if (not text.is_empty()) 527 if (not text.is_empty())
792 } 787 }
793 } 788 }
794 break; 789 break;
795 790
796 case '\n': 791 case '\n':
792 case '\r':
797 case KEY_ENTER: 793 case KEY_ENTER:
798 switch (CurrentInputState) 794 switch (CurrentInputState)
799 { 795 {
800 case INPUTSTATE_CONFIRM_DISCONNECTION: 796 case INPUTSTATE_CONFIRM_DISCONNECTION:
801 break; // handled above 797 break; // handled above
826 set_input_state (INPUTSTATE_NORMAL); 822 set_input_state (INPUTSTATE_NORMAL);
827 } 823 }
828 break; 824 break;
829 825
830 case INPUTSTATE_NORMAL: 826 case INPUTSTATE_NORMAL:
831 if (Session.send_command (current_input())) 827 if (current_input()[0] == '/')
828 {
829 handle_command(current_input());
830 InputHistory.insert (0, "");
831 NeedInputRender = true;
832 }
833 else if (Session.send_command (current_input()))
832 { 834 {
833 InputHistory.insert (0, ""); 835 InputHistory.insert (0, "");
834 NeedInputRender = true; 836 NeedInputRender = true;
835 } 837 }
836 break; 838 break;
840 case 'N' - 'A' + 1: // ^N 842 case 'N' - 'A' + 1: // ^N
841 if (CurrentInputState == INPUTSTATE_NORMAL) 843 if (CurrentInputState == INPUTSTATE_NORMAL)
842 safe_disconnect ([&]() {set_input_state (INPUTSTATE_ADDRESS);}); 844 safe_disconnect ([&]() {set_input_state (INPUTSTATE_ADDRESS);});
843 break; 845 break;
844 846
845 case '\e': // Escape 847 case '\x1b': // Escape
846 // We may have an alt key coming 848 // We may have an alt key coming
847 ch = ::getch(); 849 ch = ::getch();
848 850
849 if (ch != ERR) 851 if (ch != ERR)
850 { 852 {
945 va_end (args); 947 va_end (args);
946 } 948 }
947 949
948 // ------------------------------------------------------------------------------------------------- 950 // -------------------------------------------------------------------------------------------------
949 // 951 //
950 void Interface::print_to_console (String a) 952 void Interface::print_to_console (String message)
951 { 953 {
952 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". 954 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C".
953 // Let's correct that on our end and hope this won't cause conflicts. 955 // Let's correct that on our end and hope this won't cause conflicts.
954 a.replace ("\\c", "\x1C"); 956 message.replace ("\\c", "\x1C");
955 957
956 for (char ch : a) 958 for (int i = 0; i < message.length(); ++i)
957 { 959 {
960 char ch = message[i];
961
958 if (ch == '\n') 962 if (ch == '\n')
959 { 963 {
960 OutputLines.last().finalize(); 964 OutputLines.last().finalize();
961 OutputLines << ColoredLine(); 965 OutputLines << ColoredLine();
962 continue; 966 continue;
1031 void Interface::disconnected() 1035 void Interface::disconnected()
1032 { 1036 {
1033 Session.disconnect(); 1037 Session.disconnect();
1034 set_input_state (INPUTSTATE_NORMAL); 1038 set_input_state (INPUTSTATE_NORMAL);
1035 } 1039 }
1040
1041 // -------------------------------------------------------------------------------------------------
1042 //
1043 void Interface::handle_command(const String& input)
1044 {
1045 if (input[0] != '/')
1046 return;
1047
1048 StringList args = input.right(input.length() - 1).split(" ");
1049 String command = args[0].to_lowercase();
1050 args.remove_at(0);
1051
1052 if (command == "connect")
1053 {
1054 if (args.size() != 2)
1055 print_error("Usage: /connect <address> <password>");
1056 else
1057 {
1058 IPAddress address;
1059
1060 try
1061 {
1062 address = IPAddress::from_string(args[0]);
1063 }
1064 catch (std::exception& e)
1065 {
1066 print_error("%s\n", e.what());
1067 return;
1068 }
1069
1070 if (address.port == 0)
1071 address.port = 10666;
1072
1073 Session.set_password(args[1]);
1074 Session.disconnect();
1075 Session.connect(CurrentAddress = address);
1076 }
1077 }
1078 else if (command == "quit")
1079 {
1080 Session.disconnect();
1081 endwin();
1082 throw Exitception();
1083 }
1084 else if (command == "watch")
1085 {
1086 if (not args.is_empty())
1087 Session.request_watch(args);
1088 else
1089 print_error("No CVars to watch.\n");
1090 }
1091 else
1092 print_error("Unknown command %s\n", command.chars());
1093 }
1094
1095 END_ZFC_NAMESPACE

mercurial