35 #include "network/rconsession.h" |
35 #include "network/rconsession.h" |
36 #include "network/ipaddress.h" |
36 #include "network/ipaddress.h" |
37 #include "coloredline.h" |
37 #include "coloredline.h" |
38 BEGIN_ZFC_NAMESPACE |
38 BEGIN_ZFC_NAMESPACE |
39 |
39 |
40 static const int g_pageSize = 10; |
40 static const int PAGE_SIZE = 10; |
41 |
41 |
42 // ------------------------------------------------------------------------------------------------- |
42 // ------------------------------------------------------------------------------------------------- |
43 // |
43 // |
44 chtype Interface::color_pair (Color fg, Color bg) |
44 chtype Interface::color_pair (Color fg, Color bg) |
45 { |
45 { |
46 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg)); |
46 if (fg == DEFAULT && bg == DEFAULT) |
|
47 return 0; |
|
48 else |
|
49 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg)); |
47 } |
50 } |
48 |
51 |
49 // ------------------------------------------------------------------------------------------------- |
52 // ------------------------------------------------------------------------------------------------- |
50 // |
53 // |
51 const String& Interface::current_input() |
54 const String& Interface::current_input() |
52 { |
55 { |
53 return InputHistory[InputCursor]; |
56 return m_inputHistory[m_inputCursor]; |
54 } |
57 } |
55 |
58 |
56 // ------------------------------------------------------------------------------------------------- |
59 // ------------------------------------------------------------------------------------------------- |
57 // |
60 // |
58 // Makes current_input() the lastmost input (so that we won't modify history) |
61 // Makes current_input() the lastmost input (so that we won't modify history) |
59 // |
62 // |
60 void Interface::detach_input() |
63 void Interface::detach_input() |
61 { |
64 { |
62 if (InputCursor > 0) |
65 if (m_inputCursor > 0) |
63 { |
66 { |
64 InputHistory[0] = current_input(); |
67 m_inputHistory[0] = current_input(); |
65 InputCursor = 0; |
68 m_inputCursor = 0; |
66 } |
69 } |
67 } |
70 } |
68 |
71 |
69 // ------------------------------------------------------------------------------------------------- |
72 // ------------------------------------------------------------------------------------------------- |
70 // A version of current_input() that allows changing the contents of it. |
73 // A version of current_input() that allows changing the contents of it. |
71 // |
74 // |
72 String& Interface::mutable_current_input() |
75 String& Interface::mutable_current_input() |
73 { |
76 { |
74 detach_input(); |
77 detach_input(); |
75 return InputHistory[InputCursor]; |
78 return m_inputHistory[m_inputCursor]; |
76 } |
79 } |
77 |
80 |
78 // ------------------------------------------------------------------------------------------------- |
81 // ------------------------------------------------------------------------------------------------- |
79 // |
82 // |
80 void Interface::move_input_cursor (int delta) |
83 void Interface::move_input_cursor (int delta) |
81 { |
84 { |
82 // No input history when inputting addresses or passwords |
85 // No input history when inputting addresses or passwords |
83 if (CurrentInputState != INPUTSTATE_NORMAL) |
86 if (m_inputState != INPUTSTATE_NORMAL) |
84 { |
87 { |
85 InputCursor = 0; |
88 m_inputCursor = 0; |
86 return; |
89 return; |
87 } |
90 } |
88 |
91 |
89 int oldcursor = InputCursor; |
92 int oldcursor = m_inputCursor; |
90 InputCursor = clamp (InputCursor + delta, 0, InputHistory.size() - 1); |
93 m_inputCursor = clamp (m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
91 |
94 |
92 if (InputCursor != oldcursor) |
95 if (m_inputCursor != oldcursor) |
93 { |
96 { |
94 CursorPosition = current_input().length(); |
97 m_cursorPosition = current_input().length(); |
95 NeedInputRender = true; |
98 m_needInputRender = true; |
96 } |
99 } |
97 } |
100 } |
98 |
101 |
99 // ------------------------------------------------------------------------------------------------- |
102 // ------------------------------------------------------------------------------------------------- |
100 // |
103 // |
101 String Interface::prompt_string() |
104 String Interface::prompt_string() |
102 { |
105 { |
103 String prompt; |
106 String prompt; |
104 |
107 |
105 switch (CurrentInputState) |
108 switch (m_inputState) |
106 { |
109 { |
107 case INPUTSTATE_NORMAL: prompt = ">"; break; |
110 case INPUTSTATE_NORMAL: prompt = ">"; break; |
108 case INPUTSTATE_ADDRESS: prompt = "address:"; break; |
111 case INPUTSTATE_ADDRESS: prompt = "address:"; break; |
109 case INPUTSTATE_PASSWORD: prompt = "password:"; break; |
112 case INPUTSTATE_PASSWORD: prompt = "password:"; break; |
110 case INPUTSTATE_CONFIRM_DISCONNECTION: break; |
113 case INPUTSTATE_CONFIRM_DISCONNECTION: break; |
117 // |
120 // |
118 void Interface::set_input_state (InputState newstate) |
121 void Interface::set_input_state (InputState newstate) |
119 { |
122 { |
120 // Clear the input row (unless going to or from confirm state) |
123 // Clear the input row (unless going to or from confirm state) |
121 if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION |
124 if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION |
122 and CurrentInputState != INPUTSTATE_CONFIRM_DISCONNECTION) |
125 and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION) |
123 { |
126 { |
124 InputCursor = 0; |
127 m_inputCursor = 0; |
125 mutable_current_input().clear(); |
128 mutable_current_input().clear(); |
126 } |
129 } |
127 |
130 |
128 switch (newstate) |
131 switch (newstate) |
129 { |
132 { |
130 case INPUTSTATE_ADDRESS: |
133 case INPUTSTATE_ADDRESS: |
131 if (CurrentAddress.host != 0) |
134 if (m_remoteAddress.host != 0) |
132 mutable_current_input() = CurrentAddress.to_string (IPAddress::WITH_PORT); |
135 mutable_current_input() = m_remoteAddress.to_string (IPAddress::WITH_PORT); |
133 break; |
136 break; |
134 |
137 |
135 default: |
138 default: |
136 break; |
139 break; |
137 } |
140 } |
138 |
141 |
139 CurrentInputState = newstate; |
142 m_inputState = newstate; |
140 NeedInputRender = true; |
143 m_needInputRender = true; |
141 } |
144 } |
142 |
145 |
143 // ------------------------------------------------------------------------------------------------- |
146 // ------------------------------------------------------------------------------------------------- |
144 // |
147 // |
145 Interface::Interface() : |
148 Interface::Interface() : |
146 InputCursor (0), |
149 m_inputCursor (0), |
147 CursorPosition (0), |
150 m_cursorPosition (0), |
148 InputPanning (0), |
151 m_inputPanning (0), |
149 NeedRefresh (false), |
152 m_needRefresh (false), |
150 NeedStatusBarRender (false), |
153 m_needStatusBarRender (false), |
151 NeedInputRender (false), |
154 m_needInputRender (false), |
152 NeedOutputRender (false), |
155 m_needOutputRender (false), |
153 NeedNicklistRender (false), |
156 m_needNicklistRender (false), |
154 OutputScroll (0), |
157 m_outputScroll (0), |
155 CurrentInputState (INPUTSTATE_NORMAL), |
158 m_inputState (INPUTSTATE_NORMAL), |
156 DisconnectConfirmFunction (nullptr) |
159 m_disconnectCallback (nullptr) |
157 { |
160 { |
158 ::initscr(); |
161 ::initscr(); |
159 ::raw(); |
162 ::raw(); |
160 ::keypad (stdscr, true); |
163 ::keypad (stdscr, true); |
161 ::noecho(); |
164 ::noecho(); |
162 ::refresh(); |
165 ::refresh(); |
163 ::timeout (0); |
166 ::timeout (0); |
164 InputHistory.clear(); |
167 m_inputHistory.clear(); |
165 InputHistory << ""; |
168 m_inputHistory << ""; |
166 OutputLines.clear(); |
169 m_outputLines.clear(); |
167 OutputLines << ColoredLine(); |
170 m_outputLines << ColoredLine(); |
168 Session.set_interface (this); |
171 m_session.set_interface (this); |
169 Title.sprintf (APPNAME " %s (%s)", full_version_string(), changeset_date_string()); |
172 reset_title(); |
170 |
173 |
171 if (::has_colors()) |
174 if (::has_colors()) |
172 { |
175 { |
173 ::start_color(); |
176 ::start_color(); |
174 ::use_default_colors(); |
177 bool hasDefaultColors = (::use_default_colors() == OK); |
175 |
178 int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE; |
176 int defaultFg = (use_default_colors() == OK) ? -1 : COLOR_WHITE; |
179 int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK; |
177 int defaultBg = (use_default_colors() == OK) ? -1 : COLOR_BLACK; |
|
178 |
180 |
179 // Initialize color pairs |
181 // Initialize color pairs |
180 for (int i = 0; i < NUM_COLORS; ++i) |
182 for (int i = 0; i < NUM_COLORS; ++i) |
181 for (int j = 0; j < NUM_COLORS; ++j) |
183 for (int j = 0; j < NUM_COLORS; ++j) |
182 { |
184 { |
183 int pairnum = 1 + (i * NUM_COLORS + j); |
185 int pairnum = 1 + (i * NUM_COLORS + j); |
184 int fg = (i == DEFAULT) ? defaultFg : i; |
186 int fg = (i == DEFAULT) ? defaultFg : i; |
185 int bg = (j == DEFAULT) ? defaultBg : j; |
187 int bg = (j == DEFAULT) ? defaultBg : j; |
186 |
188 |
187 if (::init_pair (pairnum, fg, bg) == ERR) |
189 if (fg != -1 || bg != -1) |
188 print ("Unable to initialize color pair %d (%d, %d)\n", pairnum, fg, bg); |
190 { |
189 } |
191 if (::init_pair (pairnum, fg, bg) == ERR) |
190 } |
192 print_warning ("Unable to initialize color pair %d (%d, %d)\n", pairnum, fg, bg); |
191 else |
193 } |
192 { |
194 } |
193 print ("This terminal does not appear to support colors.\n"); |
|
194 } |
195 } |
195 |
196 |
196 render_full(); |
197 render_full(); |
197 refresh(); |
198 refresh(); |
198 NeedRefresh = false; |
199 m_needRefresh = false; |
199 } |
200 } |
200 |
201 |
201 // ------------------------------------------------------------------------------------------------- |
202 // ------------------------------------------------------------------------------------------------- |
202 // |
203 // |
203 void Interface::render_titlebar() |
204 void Interface::render_titlebar() |
204 { |
205 { |
205 if (Title.length() <= COLS) |
206 if (m_title.length() <= COLS) |
206 { |
207 { |
207 chtype pair = color_pair (WHITE, BLUE); |
208 chtype pair = color_pair (WHITE, BLUE); |
208 int startx = (COLS - Title.length()) / 2; |
209 int startx = (COLS - m_title.length()) / 2; |
209 int endx = startx + Title.length(); |
210 int endx = startx + m_title.length(); |
210 attron (pair); |
211 attron (pair); |
211 mvprintw (0, startx, "%s", Title.chars()); |
212 mvprintw (0, startx, "%s", m_title.chars()); |
212 mvhline (0, 0, ' ', startx); |
213 mvhline (0, 0, ' ', startx); |
213 mvhline (0, endx, ' ', COLS - endx); |
214 mvhline (0, endx, ' ', COLS - endx); |
214 attroff (pair); |
215 attroff (pair); |
215 } |
216 } |
216 |
217 |
217 NeedRefresh = true; |
218 m_needRefresh = true; |
218 } |
219 } |
219 |
220 |
220 // ------------------------------------------------------------------------------------------------- |
221 // ------------------------------------------------------------------------------------------------- |
221 // |
222 // |
222 void Interface::set_title (const String& title) |
223 void Interface::set_title (const String& title) |
223 { |
224 { |
224 Title = title; |
225 m_title = title; |
225 render_titlebar(); |
226 render_titlebar(); |
226 } |
227 } |
227 |
228 |
228 // ------------------------------------------------------------------------------------------------- |
229 // ------------------------------------------------------------------------------------------------- |
229 // |
230 // |
230 void Interface::safe_disconnect (std::function<void()> afterwards) |
231 void Interface::safe_disconnect (std::function<void(bool)> afterwards) |
231 { |
232 { |
232 if (Session.is_active()) |
233 if (m_session.is_active()) |
233 { |
234 { |
234 DisconnectConfirmFunction = afterwards; |
235 m_disconnectCallback = afterwards; |
235 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); |
236 set_input_state (INPUTSTATE_CONFIRM_DISCONNECTION); |
236 } |
237 } |
237 else |
238 else |
238 afterwards(); |
239 afterwards(false); |
239 } |
240 } |
240 |
241 |
241 // ------------------------------------------------------------------------------------------------- |
242 // ------------------------------------------------------------------------------------------------- |
242 // |
243 // |
243 int Interface::nicklist_width() |
244 int Interface::nicklist_width() |
404 } |
405 } |
405 |
406 |
406 y++; |
407 y++; |
407 } |
408 } |
408 |
409 |
409 NeedNicklistRender = false; |
410 m_needNicklistRender = false; |
410 NeedRefresh = true; |
411 m_needRefresh = true; |
411 } |
412 } |
412 |
413 |
413 // ------------------------------------------------------------------------------------------------- |
414 // ------------------------------------------------------------------------------------------------- |
414 // |
415 // |
415 void Interface::render_input() |
416 void Interface::render_input() |
416 { |
417 { |
417 chtype promptColor = color_pair (WHITE, BLUE); |
418 chtype promptColor = color_pair (WHITE, BLUE); |
418 |
419 |
419 // If we're asking the user if they want to disconnect, we don't render any input strings, |
420 // If we're asking the user if they want to disconnect, we don't render any input strings, |
420 // just the confirmation message. |
421 // just the confirmation message. |
421 if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
422 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
422 { |
423 { |
423 attron (promptColor); |
424 attron (promptColor); |
424 mvhline (LINES - 2, 0, ' ', COLS); |
425 mvhline (LINES - 2, 0, ' ', COLS); |
425 mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
426 mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
426 attroff (promptColor); |
427 attroff (promptColor); |
427 NeedRefresh = true; |
428 m_needRefresh = true; |
428 return; |
429 return; |
429 } |
430 } |
430 |
431 |
431 String prompt = prompt_string(); |
432 String prompt = prompt_string(); |
432 int displayLength = COLS - prompt.length() - 2; |
433 int displayLength = COLS - prompt.length() - 2; |
433 String displayString = current_input(); |
434 String displayString = current_input(); |
434 int y = LINES - 2; |
435 int y = LINES - 2; |
435 |
436 |
436 // If we're inputting a password, replace it with asterisks |
437 // If we're inputting a password, replace it with asterisks |
437 if (CurrentInputState == INPUTSTATE_PASSWORD) |
438 if (m_inputState == INPUTSTATE_PASSWORD) |
438 { |
439 { |
439 for (int i = 0; i < displayString.length(); ++i) |
440 for (int i = 0; i < displayString.length(); ++i) |
440 displayString[i] = '*'; |
441 displayString[i] = '*'; |
441 } |
442 } |
442 |
443 |
443 // Ensure the cursor is within bounds |
444 // Ensure the cursor is within bounds |
444 CursorPosition = clamp (CursorPosition, 0, displayString.length()); |
445 m_cursorPosition = clamp (m_cursorPosition, 0, displayString.length()); |
445 |
446 |
446 // Ensure that the cursor is always in view, adjust panning if this is not the case |
447 // Ensure that the cursor is always in view, adjust panning if this is not the case |
447 if (CursorPosition > InputPanning + displayLength) |
448 if (m_cursorPosition > m_inputPanning + displayLength) |
448 InputPanning = CursorPosition - displayLength; // cursor went too far right |
449 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
449 else if (CursorPosition < InputPanning) |
450 else if (m_cursorPosition < m_inputPanning) |
450 InputPanning = CursorPosition; // cursor went past the pan value to the left |
451 m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left |
451 |
452 |
452 // What part of the string to draw? |
453 // What part of the string to draw? |
453 int start = InputPanning; |
454 int start = m_inputPanning; |
454 int end = min<int> (displayString.length(), start + displayLength); |
455 int end = min<int> (displayString.length(), start + displayLength); |
455 assert (CursorPosition >= start and CursorPosition <= end); |
456 assert (m_cursorPosition >= start and m_cursorPosition <= end); |
456 |
457 |
457 // Render the input string |
458 // Render the input string |
458 mvhline (LINES - 2, 0, ' ', COLS); |
459 mvhline (LINES - 2, 0, ' ', COLS); |
459 mvprintw (y, prompt.length() + 1, "%s", displayString.mid (start, end).chars()); |
460 mvprintw (y, prompt.length() + 1, "%s", displayString.mid (start, end).chars()); |
460 |
461 |
463 mvprintw (y, 0, "%s", prompt.chars()); |
464 mvprintw (y, 0, "%s", prompt.chars()); |
464 attroff (promptColor); |
465 attroff (promptColor); |
465 |
466 |
466 // Store in memory where the cursor is now (so that we can re-draw it to position the terminal |
467 // Store in memory where the cursor is now (so that we can re-draw it to position the terminal |
467 // cursor). |
468 // cursor). |
468 CursorCharacter.ch = CursorPosition != 0 ? displayString[CursorPosition - 1] : '\0'; |
469 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
469 CursorCharacter.x = prompt.length() + (CursorPosition - InputPanning); |
470 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
470 NeedRefresh = true; |
471 m_needRefresh = true; |
471 NeedInputRender = false; |
472 m_needInputRender = false; |
472 } |
473 } |
473 |
474 |
474 // ------------------------------------------------------------------------------------------------- |
475 // ------------------------------------------------------------------------------------------------- |
475 // |
476 // |
476 void Interface::render_statusbar() |
477 void Interface::render_statusbar() |
477 { |
478 { |
478 chtype color = color_pair (WHITE, BLUE); |
479 chtype color = color_pair (WHITE, BLUE); |
479 int y = LINES - 1; |
480 int y = LINES - 1; |
480 attron (color); |
481 attron (color); |
481 mvhline (y, 0, ' ', COLS); |
482 mvhline (y, 0, ' ', COLS); |
482 mvprintw (y, 0, "%s", StatusBarText.chars()); |
483 mvprintw (y, 0, "%s", m_statusBarText.chars()); |
483 attroff (color); |
484 attroff (color); |
484 NeedRefresh = true; |
485 m_needRefresh = true; |
485 NeedStatusBarRender = false; |
486 m_needStatusBarRender = false; |
486 } |
487 } |
487 |
488 |
488 // ------------------------------------------------------------------------------------------------- |
489 // ------------------------------------------------------------------------------------------------- |
489 // |
490 // |
490 void Interface::update_statusbar() |
491 void Interface::update_statusbar() |
491 { |
492 { |
492 String text; |
493 String text; |
493 |
494 |
494 switch (Session.state()) |
495 switch (m_session.state()) |
495 { |
496 { |
496 case RCON_DISCONNECTED: |
497 case RCON_DISCONNECTED: |
497 text = "Disconnected."; |
498 text = "Disconnected."; |
498 break; |
499 break; |
499 |
500 |
500 case RCON_CONNECTING: |
501 case RCON_CONNECTING: |
501 case RCON_AUTHENTICATING: |
502 case RCON_AUTHENTICATING: |
502 text = "Connecting to " + Session.address().to_string (IPAddress::WITH_PORT) + "..."; |
503 text = "Connecting to " + m_session.address().to_string (IPAddress::WITH_PORT) + "..."; |
503 break; |
504 break; |
504 |
505 |
505 case RCON_CONNECTED: |
506 case RCON_CONNECTED: |
506 { |
507 { |
507 String adminText; |
508 String adminText; |
508 |
509 |
509 if (Session.num_admins() == 0) |
510 if (m_session.num_admins() == 0) |
510 { |
511 { |
511 adminText = "No other admins"; |
512 adminText = "No other admins"; |
512 } |
513 } |
513 else |
514 else |
514 { |
515 { |
515 adminText.sprintf ("%d other admin%s", Session.num_admins(), |
516 adminText.sprintf ("%d other admin%s", m_session.num_admins(), |
516 Session.num_admins() != 1 ? "s" : ""); |
517 m_session.num_admins() != 1 ? "s" : ""); |
517 } |
518 } |
518 |
519 |
519 text.sprintf ("%s | %s | %s", |
520 text.sprintf ("%s | %s | %s", |
520 Session.address().to_string (IPAddress::WITH_PORT).chars(), |
521 m_session.address().to_string (IPAddress::WITH_PORT).chars(), |
521 Session.level().chars(), |
522 m_session.level().chars(), |
522 adminText.chars()); |
523 adminText.chars()); |
523 } |
524 } |
524 break; |
525 break; |
525 } |
526 } |
526 |
527 |
527 if (not text.is_empty()) |
528 if (not text.is_empty()) |
528 text += " | "; |
529 text += " | "; |
529 |
530 |
530 text += "Ctrl+N to connect, Ctrl+Q to quit"; |
531 text += "Ctrl+N to connect, Ctrl+Q to "; |
531 |
532 text += (m_session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
532 if (text != StatusBarText) |
533 |
533 { |
534 if (text != m_statusBarText) |
534 StatusBarText = text; |
535 { |
535 NeedStatusBarRender = true; |
536 m_statusBarText = text; |
|
537 m_needStatusBarRender = true; |
536 } |
538 } |
537 } |
539 } |
538 |
540 |
539 // ------------------------------------------------------------------------------------------------- |
541 // ------------------------------------------------------------------------------------------------- |
540 // |
542 // |
551 // ------------------------------------------------------------------------------------------------- |
553 // ------------------------------------------------------------------------------------------------- |
552 // |
554 // |
553 void Interface::position_cursor() |
555 void Interface::position_cursor() |
554 { |
556 { |
555 // This is only relevant if the input string is being drawn |
557 // This is only relevant if the input string is being drawn |
556 if (CurrentInputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
558 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
557 return; |
559 return; |
558 |
560 |
559 int y = LINES - 2; |
561 int y = LINES - 2; |
560 |
562 |
561 if (CursorCharacter.ch != '\0') |
563 if (m_cursorCharacter.ch != '\0') |
562 mvprintw (y, CursorCharacter.x, "%c", CursorCharacter.ch); |
564 mvprintw (y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
563 else |
565 else |
564 mvprintw (y, prompt_string().length(), " "); |
566 mvprintw (y, prompt_string().length(), " "); |
565 } |
567 } |
566 |
568 |
567 // ------------------------------------------------------------------------------------------------- |
569 // ------------------------------------------------------------------------------------------------- |
568 // |
570 // |
569 int Interface::find_previous_word() |
571 int Interface::find_previous_word() |
570 { |
572 { |
571 const String& input = current_input(); |
573 const String& input = current_input(); |
572 int pos = CursorPosition; |
574 int pos = m_cursorPosition; |
573 |
575 |
574 // Move past whitespace |
576 // Move past whitespace |
575 while (pos > 0 and isspace (input[pos - 1])) |
577 while (pos > 0 and isspace (input[pos - 1])) |
576 pos--; |
578 pos--; |
577 |
579 |
680 } |
681 } |
681 break; |
682 break; |
682 |
683 |
683 case KEY_LEFT: |
684 case KEY_LEFT: |
684 case 'B' - 'A' + 1: // readline ^B |
685 case 'B' - 'A' + 1: // readline ^B |
685 if (CursorPosition > 0) |
686 if (m_cursorPosition > 0) |
686 { |
687 { |
687 CursorPosition--; |
688 m_cursorPosition--; |
688 NeedInputRender = true; |
689 m_needInputRender = true; |
689 } |
690 } |
690 break; |
691 break; |
691 |
692 |
692 case KEY_RIGHT: |
693 case KEY_RIGHT: |
693 case 'F' - 'A' + 1: // readline ^F |
694 case 'F' - 'A' + 1: // readline ^F |
694 if (CursorPosition < current_input().length()) |
695 if (m_cursorPosition < current_input().length()) |
695 { |
696 { |
696 CursorPosition++; |
697 m_cursorPosition++; |
697 NeedInputRender = true; |
698 m_needInputRender = true; |
698 } |
699 } |
699 break; |
700 break; |
700 |
701 |
701 case KEY_DOWN: |
702 case KEY_DOWN: |
702 case KEY_UP: |
703 case KEY_UP: |
703 move_input_cursor (ch == KEY_DOWN ? -1 : 1); |
704 move_input_cursor (ch == KEY_DOWN ? -1 : 1); |
704 break; |
705 break; |
705 |
706 |
706 case KEY_HOME: |
707 case KEY_HOME: |
707 case 'A' - 'A' + 1: // readline ^A |
708 case 'A' - 'A' + 1: // readline ^A |
708 if (CursorPosition != 0) |
709 if (m_cursorPosition != 0) |
709 { |
710 { |
710 CursorPosition = 0; |
711 m_cursorPosition = 0; |
711 NeedInputRender = true; |
712 m_needInputRender = true; |
712 } |
713 } |
713 break; |
714 break; |
714 |
715 |
715 case KEY_END: |
716 case KEY_END: |
716 case 'E' - 'A' + 1: // readline ^E |
717 case 'E' - 'A' + 1: // readline ^E |
717 if (CursorPosition != current_input().length()) |
718 if (m_cursorPosition != current_input().length()) |
718 { |
719 { |
719 CursorPosition = current_input().length(); |
720 m_cursorPosition = current_input().length(); |
720 NeedInputRender = true; |
721 m_needInputRender = true; |
721 } |
722 } |
722 break; |
723 break; |
723 |
724 |
724 case KEY_BACKSPACE: |
725 case KEY_BACKSPACE: |
725 case '\b': |
726 case '\b': |
726 if (CursorPosition > 0) |
727 if (m_cursorPosition > 0) |
727 { |
728 { |
728 mutable_current_input().remove_at (--CursorPosition); |
729 mutable_current_input().remove_at (--m_cursorPosition); |
729 NeedInputRender = true; |
730 m_needInputRender = true; |
730 } |
731 } |
731 break; |
732 break; |
732 |
733 |
733 case KEY_DC: |
734 case KEY_DC: |
734 case 'D' - 'A' + 1: // readline ^D |
735 case 'D' - 'A' + 1: // readline ^D |
735 if (CursorPosition < current_input().length()) |
736 if (m_cursorPosition < current_input().length()) |
736 { |
737 { |
737 mutable_current_input().remove_at (CursorPosition); |
738 mutable_current_input().remove_at (m_cursorPosition); |
738 NeedInputRender = true; |
739 m_needInputRender = true; |
739 } |
740 } |
740 break; |
741 break; |
741 |
742 |
742 case KEY_PPAGE: |
743 case KEY_PPAGE: |
743 OutputScroll += min (g_pageSize, LINES / 2); |
744 m_outputScroll += min (PAGE_SIZE, LINES / 2); |
744 NeedOutputRender = true; |
745 m_needOutputRender = true; |
745 break; |
746 break; |
746 |
747 |
747 case KEY_NPAGE: |
748 case KEY_NPAGE: |
748 OutputScroll -= min (g_pageSize, LINES / 2); |
749 m_outputScroll -= min (PAGE_SIZE, LINES / 2); |
749 NeedOutputRender = true; |
750 m_needOutputRender = true; |
750 break; |
751 break; |
751 |
752 |
752 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
753 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
753 if (CursorPosition > 0) |
754 if (m_cursorPosition > 0) |
754 { |
755 { |
755 yank (0, CursorPosition); |
756 yank (0, m_cursorPosition); |
756 CursorPosition = 0; |
757 m_cursorPosition = 0; |
757 } |
758 } |
758 break; |
759 break; |
759 |
760 |
760 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
761 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
761 yank (CursorPosition, mutable_current_input().length()); |
762 yank (m_cursorPosition, mutable_current_input().length()); |
762 break; |
763 break; |
763 |
764 |
764 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
765 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
765 yank (find_previous_word(), CursorPosition); |
766 yank (find_previous_word(), m_cursorPosition); |
766 break; |
767 break; |
767 |
768 |
768 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
769 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
769 if (not PasteBuffer.is_empty()) |
770 if (not m_pasteBuffer.is_empty()) |
770 { |
771 { |
771 mutable_current_input().insert (CursorPosition, PasteBuffer); |
772 mutable_current_input().insert (m_cursorPosition, m_pasteBuffer); |
772 CursorPosition += PasteBuffer.length(); |
773 m_cursorPosition += m_pasteBuffer.length(); |
773 NeedInputRender = true; |
774 m_needInputRender = true; |
774 } |
775 } |
775 break; |
776 break; |
776 |
777 |
777 case '\t': |
778 case '\t': |
778 { |
779 { |
779 int space = current_input().find (" "); |
780 int space = current_input().find (" "); |
780 |
781 |
781 if (CurrentInputState == INPUTSTATE_NORMAL |
782 if (m_inputState == INPUTSTATE_NORMAL |
782 and CursorPosition > 0 |
783 and m_cursorPosition > 0 |
783 and (space == -1 or space >= CursorPosition)) |
784 and (space == -1 or space >= m_cursorPosition)) |
784 { |
785 { |
785 String start = current_input().mid (0, CursorPosition); |
786 String start = current_input().mid (0, m_cursorPosition); |
786 Session.request_tab_complete (start); |
787 m_session.request_tab_complete (start); |
787 } |
788 } |
788 } |
789 } |
789 break; |
790 break; |
790 |
791 |
791 case '\n': |
792 case '\n': |
792 case '\r': |
793 case '\r': |
793 case KEY_ENTER: |
794 case KEY_ENTER: |
794 switch (CurrentInputState) |
795 switch (m_inputState) |
795 { |
796 { |
796 case INPUTSTATE_CONFIRM_DISCONNECTION: |
797 case INPUTSTATE_CONFIRM_DISCONNECTION: |
797 break; // handled above |
798 break; // handled above |
798 |
799 |
799 case INPUTSTATE_ADDRESS: |
800 case INPUTSTATE_ADDRESS: |
800 try |
801 try |
801 { |
802 { |
802 CurrentAddress = IPAddress::from_string (current_input()); |
803 m_remoteAddress = IPAddress::from_string (current_input()); |
803 } |
804 } |
804 catch (std::exception& e) |
805 catch (std::exception& e) |
805 { |
806 { |
806 print ("%s\n", e.what()); |
807 print ("%s\n", e.what()); |
807 return; |
808 return; |
808 } |
809 } |
809 |
810 |
810 if (CurrentAddress.port == 0) |
811 if (m_remoteAddress.port == 0) |
811 CurrentAddress.port = 10666; |
812 m_remoteAddress.port = 10666; |
812 |
813 |
813 set_input_state (INPUTSTATE_PASSWORD); |
814 set_input_state (INPUTSTATE_PASSWORD); |
814 break; |
815 break; |
815 |
816 |
816 case INPUTSTATE_PASSWORD: |
817 case INPUTSTATE_PASSWORD: |
817 if (CurrentInputState == INPUTSTATE_PASSWORD and not current_input().is_empty()) |
818 if (m_inputState == INPUTSTATE_PASSWORD and not current_input().is_empty()) |
818 { |
819 { |
819 Session.disconnect(); |
820 m_session.disconnect(); |
820 Session.set_password (current_input()); |
821 m_session.set_password (current_input()); |
821 Session.connect (CurrentAddress); |
822 m_session.connect (m_remoteAddress); |
822 set_input_state (INPUTSTATE_NORMAL); |
823 set_input_state (INPUTSTATE_NORMAL); |
823 } |
824 } |
824 break; |
825 break; |
825 |
826 |
826 case INPUTSTATE_NORMAL: |
827 case INPUTSTATE_NORMAL: |
827 if (current_input()[0] == '/') |
828 if (current_input()[0] == '/') |
828 { |
829 { |
829 handle_command(current_input()); |
830 handle_command(current_input()); |
830 InputHistory.insert (0, ""); |
831 flush_input(); |
831 NeedInputRender = true; |
832 } |
832 } |
833 else if (m_session.send_command (current_input())) |
833 else if (Session.send_command (current_input())) |
834 { |
834 { |
835 flush_input(); |
835 InputHistory.insert (0, ""); |
|
836 NeedInputRender = true; |
|
837 } |
836 } |
838 break; |
837 break; |
839 } |
838 } |
840 break; |
839 break; |
841 |
840 |
842 case 'N' - 'A' + 1: // ^N |
841 case 'N' - 'A' + 1: // ^N |
843 if (CurrentInputState == INPUTSTATE_NORMAL) |
842 if (m_inputState == INPUTSTATE_NORMAL) |
844 safe_disconnect ([&]() {set_input_state (INPUTSTATE_ADDRESS);}); |
843 safe_disconnect ([&](bool){set_input_state (INPUTSTATE_ADDRESS);}); |
845 break; |
844 break; |
846 |
845 |
847 case '\x1b': // Escape |
846 case '\x1b': // Escape |
848 // We may have an alt key coming |
847 // We may have an alt key coming |
849 ch = ::getch(); |
848 ch = ::getch(); |
853 switch (ch) |
852 switch (ch) |
854 { |
853 { |
855 case 'b': |
854 case 'b': |
856 case 'B': |
855 case 'B': |
857 // readline alt-b - move one word to the left |
856 // readline alt-b - move one word to the left |
858 CursorPosition = find_previous_word(); |
857 m_cursorPosition = find_previous_word(); |
859 NeedInputRender = true; |
858 m_needInputRender = true; |
860 break; |
859 break; |
861 |
860 |
862 case 'f': |
861 case 'f': |
863 case 'F': |
862 case 'F': |
864 // readline alt-f - move one word to the right |
863 // readline alt-f - move one word to the right |
865 CursorPosition = find_next_word(); |
864 m_cursorPosition = find_next_word(); |
866 NeedInputRender = true; |
865 m_needInputRender = true; |
867 break; |
866 break; |
868 |
867 |
869 case 'd': |
868 case 'd': |
870 case 'D': |
869 case 'D': |
871 // readline alt-d - delete from here till next word boundary |
870 // readline alt-d - delete from here till next word boundary |
872 yank (CursorPosition, find_next_word()); |
871 yank (m_cursorPosition, find_next_word()); |
873 break; |
872 break; |
|
873 |
|
874 case KEY_BACKSPACE: // alt+backspace, remove previous word |
|
875 case '\b': |
|
876 yank (find_previous_word(), m_cursorPosition); |
|
877 break; |
874 } |
878 } |
875 } |
879 } |
876 else |
880 else |
877 { |
881 { |
878 // No alt-key, handle pure escape |
882 // No alt-key, handle pure escape |
879 if (CurrentInputState == INPUTSTATE_PASSWORD) |
883 if (m_inputState == INPUTSTATE_PASSWORD) |
880 set_input_state (INPUTSTATE_ADDRESS); |
884 set_input_state (INPUTSTATE_ADDRESS); |
881 else if (CurrentInputState == INPUTSTATE_ADDRESS) |
885 else if (m_inputState == INPUTSTATE_ADDRESS) |
882 set_input_state (INPUTSTATE_NORMAL); |
886 set_input_state (INPUTSTATE_NORMAL); |
883 } |
887 } |
884 break; |
888 break; |
885 } |
889 } |
886 |
890 |
913 print_to_console (message); |
917 print_to_console (message); |
914 } |
918 } |
915 |
919 |
916 // ------------------------------------------------------------------------------------------------- |
920 // ------------------------------------------------------------------------------------------------- |
917 // |
921 // |
918 void __cdecl Interface::print (const char* fmtstr, ...) |
922 void __cdecl Interface::print_text (const char* fmtstr, ...) |
919 { |
923 { |
920 va_list args; |
924 va_list args; |
921 va_start (args, fmtstr); |
925 va_start (args, fmtstr); |
922 vprint (fmtstr, args); |
926 vprint (fmtstr, args); |
923 va_end (args); |
927 va_end (args); |
924 } |
928 } |
925 |
929 |
926 // ------------------------------------------------------------------------------------------------- |
930 // ------------------------------------------------------------------------------------------------- |
927 // |
931 // |
|
932 void __cdecl Interface::print (const char* fmtstr, ...) |
|
933 { |
|
934 va_list args; |
|
935 va_start (args, fmtstr); |
|
936 print_to_console (TEXTCOLOR_BrightBlue); |
|
937 vprint (fmtstr, args); |
|
938 va_end (args); |
|
939 } |
|
940 |
|
941 // ------------------------------------------------------------------------------------------------- |
|
942 // |
928 void __cdecl Interface::print_warning (const char* fmtstr, ...) |
943 void __cdecl Interface::print_warning (const char* fmtstr, ...) |
929 { |
944 { |
930 va_list args; |
945 va_list args; |
931 va_start (args, fmtstr); |
946 va_start (args, fmtstr); |
932 print_to_console (TEXTCOLOR_BrightYellow "-!- "); |
947 print_to_console (TEXTCOLOR_BrightYellow "-!- "); |
933 vprint (fmtstr, args); |
948 vprint (fmtstr, args); |
934 print_to_console (TEXTCOLOR_Reset); |
|
935 va_end (args); |
949 va_end (args); |
936 } |
950 } |
937 |
951 |
938 // ------------------------------------------------------------------------------------------------- |
952 // ------------------------------------------------------------------------------------------------- |
939 // |
953 // |
959 { |
972 { |
960 char ch = message[i]; |
973 char ch = message[i]; |
961 |
974 |
962 if (ch == '\n') |
975 if (ch == '\n') |
963 { |
976 { |
964 OutputLines.last().finalize(); |
977 m_outputLines.last().finalize(); |
965 OutputLines << ColoredLine(); |
978 m_outputLines << ColoredLine(); |
966 continue; |
979 continue; |
967 } |
980 } |
968 |
981 |
969 if (OutputLines.last().length() == 0) |
982 if (m_outputLines.last().length() == 0) |
970 { |
983 { |
971 time_t now; |
984 time_t now; |
972 time (&now); |
985 time (&now); |
973 char timestamp[32]; |
986 char timestamp[32]; |
974 strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now)); |
987 strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now)); |
975 |
988 |
976 for (char* cp = timestamp; *cp != '\0'; ++cp) |
989 for (char* cp = timestamp; *cp != '\0'; ++cp) |
977 OutputLines.last().add_char (*cp); |
990 m_outputLines.last().add_char (*cp); |
978 } |
991 } |
979 |
992 |
980 OutputLines.last().add_char (ch); |
993 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
981 } |
994 while (m_outputLines.size() > 20000) |
982 |
995 m_outputLines.remove_at(0); |
983 NeedOutputRender = true; |
996 |
|
997 m_outputLines.last().add_char (ch); |
|
998 } |
|
999 |
|
1000 m_needOutputRender = true; |
984 } |
1001 } |
985 |
1002 |
986 // ------------------------------------------------------------------------------------------------- |
1003 // ------------------------------------------------------------------------------------------------- |
987 // |
1004 // |
988 void Interface::connect (String address, String password) |
1005 void Interface::connect (String address, String password) |
989 { |
1006 { |
990 try |
1007 try |
991 { |
1008 { |
992 CurrentAddress = IPAddress::from_string (address); |
1009 m_remoteAddress = IPAddress::from_string (address); |
993 } |
1010 } |
994 catch (std::exception& e) |
1011 catch (std::exception& e) |
995 { |
1012 { |
996 print ("%s\n", e.what()); |
1013 print ("%s\n", e.what()); |
997 return; |
1014 return; |
998 } |
1015 } |
999 |
1016 |
1000 if (CurrentAddress.port == 0) |
1017 if (m_remoteAddress.port == 0) |
1001 CurrentAddress.port = 10666; |
1018 m_remoteAddress.port = 10666; |
1002 |
1019 |
1003 Session.disconnect(); |
1020 m_session.disconnect(); |
1004 Session.set_password (password); |
1021 m_session.set_password (password); |
1005 Session.connect (CurrentAddress); |
1022 m_session.connect (m_remoteAddress); |
1006 } |
1023 } |
1007 |
1024 |
1008 // ------------------------------------------------------------------------------------------------- |
1025 // ------------------------------------------------------------------------------------------------- |
1009 // |
1026 // |
1010 void Interface::set_player_names (const StringList& names) |
1027 void Interface::set_player_names (const StringList& names) |
1011 { |
1028 { |
1012 PlayerNames = names; |
1029 m_playerNames = names; |
1013 NeedNicklistRender = true; |
1030 m_needNicklistRender = true; |
1014 } |
1031 } |
1015 |
1032 |
1016 // ------------------------------------------------------------------------------------------------- |
1033 // ------------------------------------------------------------------------------------------------- |
1017 // |
1034 // |
1018 void Interface::tab_complete (const String& part, String complete) |
1035 void Interface::tab_complete (const String& part, String complete) |
1068 } |
1079 } |
1069 |
1080 |
1070 if (address.port == 0) |
1081 if (address.port == 0) |
1071 address.port = 10666; |
1082 address.port = 10666; |
1072 |
1083 |
1073 Session.set_password(args[1]); |
1084 m_session.set_password(args[1]); |
1074 Session.disconnect(); |
1085 m_session.disconnect(); |
1075 Session.connect(CurrentAddress = address); |
1086 m_session.connect(m_remoteAddress = address); |
1076 } |
1087 } |
|
1088 } |
|
1089 else if (command == "disconnect") |
|
1090 { |
|
1091 m_session.disconnect(); |
1077 } |
1092 } |
1078 else if (command == "quit") |
1093 else if (command == "quit") |
1079 { |
1094 { |
1080 Session.disconnect(); |
1095 m_session.disconnect(); |
1081 endwin(); |
1096 endwin(); |
1082 throw Exitception(); |
1097 throw Exitception(); |
1083 } |
1098 } |
1084 else if (command == "watch") |
1099 else if (command == "watch") |
1085 { |
1100 { |
1086 if (not args.is_empty()) |
1101 if (not args.is_empty()) |
1087 Session.request_watch(args); |
1102 m_session.request_watch(args); |
1088 else |
1103 else |
1089 print_error("No CVars to watch.\n"); |
1104 print_error("No CVars to watch.\n"); |
1090 } |
1105 } |
1091 else |
1106 else |
1092 print_error("Unknown command %s\n", command.chars()); |
1107 print_error("Unknown command %s\n", command.chars()); |
1093 } |
1108 } |
1094 |
1109 |
|
1110 // ------------------------------------------------------------------------------------------------- |
|
1111 // |
|
1112 void Interface::disconnected() |
|
1113 { |
|
1114 print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars()); |
|
1115 reset_title(); |
|
1116 render_full(); |
|
1117 } |
|
1118 |
|
1119 // ------------------------------------------------------------------------------------------------- |
|
1120 // |
|
1121 void Interface::reset_title() |
|
1122 { |
|
1123 m_title.sprintf ("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
|
1124 } |
|
1125 |
|
1126 // ------------------------------------------------------------------------------------------------- |
|
1127 // |
|
1128 void Interface::flush_input() |
|
1129 { |
|
1130 m_inputHistory.insert (0, ""); |
|
1131 m_inputCursor = 0; |
|
1132 m_needInputRender = true; |
|
1133 } |
|
1134 |
1095 END_ZFC_NAMESPACE |
1135 END_ZFC_NAMESPACE |