39 |
39 |
40 static const int PAGE_SIZE = 10; |
40 static const int PAGE_SIZE = 10; |
41 |
41 |
42 // ------------------------------------------------------------------------------------------------- |
42 // ------------------------------------------------------------------------------------------------- |
43 // |
43 // |
44 chtype Interface::getColorPair (Color fg, Color bg) |
44 chtype Interface::getColorPair(Color fg, Color bg) |
45 { |
45 { |
46 if (fg == DEFAULT && bg == DEFAULT) |
46 if (fg == DEFAULT && bg == DEFAULT) |
47 return 0; |
47 return 0; |
48 else |
48 else |
49 return COLOR_PAIR (1 + (int (fg) * NUM_COLORS) + int (bg)); |
49 return COLOR_PAIR(1 + (int(fg) * NUM_COLORS) + int(bg)); |
50 } |
50 } |
51 |
51 |
52 // ------------------------------------------------------------------------------------------------- |
52 // ------------------------------------------------------------------------------------------------- |
53 // |
53 // |
54 const String& Interface::getCurrentInput() |
54 const String& Interface::getCurrentInput() |
78 return m_inputHistory[m_inputCursor]; |
78 return m_inputHistory[m_inputCursor]; |
79 } |
79 } |
80 |
80 |
81 // ------------------------------------------------------------------------------------------------- |
81 // ------------------------------------------------------------------------------------------------- |
82 // |
82 // |
83 void Interface::moveInputCursor (int delta) |
83 void Interface::moveInputCursor(int delta) |
84 { |
84 { |
85 // No input history when inputting addresses or passwords |
85 // No input history when inputting addresses or passwords |
86 if (m_inputState != INPUTSTATE_NORMAL) |
86 if (m_inputState != INPUTSTATE_NORMAL) |
87 { |
87 { |
88 m_inputCursor = 0; |
88 m_inputCursor = 0; |
89 return; |
89 return; |
90 } |
90 } |
91 |
91 |
92 int oldcursor = m_inputCursor; |
92 int oldcursor = m_inputCursor; |
93 m_inputCursor = clamp (m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
93 m_inputCursor = clamp(m_inputCursor + delta, 0, m_inputHistory.size() - 1); |
94 |
94 |
95 if (m_inputCursor != oldcursor) |
95 if (m_inputCursor != oldcursor) |
96 { |
96 { |
97 m_cursorPosition = getCurrentInput().length(); |
97 m_cursorPosition = getCurrentInput().length(); |
98 m_needInputRender = true; |
98 m_needInputRender = true; |
144 } |
144 } |
145 |
145 |
146 // ------------------------------------------------------------------------------------------------- |
146 // ------------------------------------------------------------------------------------------------- |
147 // |
147 // |
148 Interface::Interface() : |
148 Interface::Interface() : |
149 m_inputCursor (0), |
149 m_inputCursor(0), |
150 m_cursorPosition (0), |
150 m_cursorPosition(0), |
151 m_inputPanning (0), |
151 m_inputPanning(0), |
152 m_needRefresh (false), |
152 m_needRefresh(false), |
153 m_needStatusBarRender (false), |
153 m_needStatusBarRender(false), |
154 m_needInputRender (false), |
154 m_needInputRender(false), |
155 m_needOutputRender (false), |
155 m_needOutputRender(false), |
156 m_needNicklistRender (false), |
156 m_needNicklistRender(false), |
157 m_outputScroll (0), |
157 m_outputScroll(0), |
158 m_inputState (INPUTSTATE_NORMAL), |
158 m_inputState(INPUTSTATE_NORMAL), |
159 m_disconnectCallback (nullptr) |
159 m_disconnectCallback(nullptr) |
160 { |
160 { |
161 ::initscr(); |
161 ::initscr(); |
162 ::raw(); |
162 ::raw(); |
163 ::keypad (stdscr, true); |
163 ::keypad(stdscr, true); |
164 ::noecho(); |
164 ::noecho(); |
165 ::refresh(); |
165 ::refresh(); |
166 ::timeout (0); |
166 ::timeout(0); |
167 m_inputHistory.clear(); |
167 m_inputHistory.clear(); |
168 m_inputHistory << ""; |
168 m_inputHistory << ""; |
169 m_outputLines.clear(); |
169 m_outputLines.clear(); |
170 m_outputLines << ColoredLine(); |
170 m_outputLines << ColoredLine(); |
171 m_session.set_interface (this); |
171 m_session.set_interface(this); |
172 resetTitle(); |
172 resetTitle(); |
173 |
173 |
174 if (::has_colors()) |
174 if (::has_colors()) |
175 { |
175 { |
176 ::start_color(); |
176 ::start_color(); |
177 bool hasDefaultColors = (::use_default_colors() == OK); |
177 bool hasDefaultColors =(::use_default_colors() == OK); |
178 int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE; |
178 int defaultFg = hasDefaultColors ? -1 : COLOR_WHITE; |
179 int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK; |
179 int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK; |
180 |
180 |
181 // Initialize color pairs |
181 // Initialize color pairs |
182 for (int i : range<int>(NUM_COLORS)) |
182 for (int i : range<int>(NUM_COLORS)) |
183 for (int j : range<int>(NUM_COLORS)) |
183 for (int j : range<int>(NUM_COLORS)) |
184 { |
184 { |
185 int pairnum = 1 + (i * NUM_COLORS + j); |
185 int pairnum = 1 + (i * NUM_COLORS + j); |
186 int fg = (i == DEFAULT) ? defaultFg : i; |
186 int fg =(i == DEFAULT) ? defaultFg : i; |
187 int bg = (j == DEFAULT) ? defaultBg : j; |
187 int bg =(j == DEFAULT) ? defaultBg : j; |
188 |
188 |
189 if (fg != -1 || bg != -1) |
189 if (fg != -1 || bg != -1) |
190 { |
190 { |
191 if (::init_pair (pairnum, fg, bg) == ERR) |
191 if (::init_pair(pairnum, fg, bg) == ERR) |
192 printWarning ("Unable to initialize color pair %d (%d, %d)\n", pairnum, fg, bg); |
192 printWarning("Unable to initialize color pair %d(%d, %d)\n", pairnum, fg, bg); |
193 } |
193 } |
194 } |
194 } |
195 } |
195 } |
196 |
196 |
197 renderFull(); |
197 renderFull(); |
203 // |
203 // |
204 void Interface::renderTitlebar() |
204 void Interface::renderTitlebar() |
205 { |
205 { |
206 if (m_title.length() <= COLS) |
206 if (m_title.length() <= COLS) |
207 { |
207 { |
208 chtype pair = getColorPair (WHITE, BLUE); |
208 chtype pair = getColorPair(WHITE, BLUE); |
209 int startx = (COLS - m_title.length()) / 2; |
209 int startx =(COLS - m_title.length()) / 2; |
210 int endx = startx + m_title.length(); |
210 int endx = startx + m_title.length(); |
211 attron (pair); |
211 attron(pair); |
212 mvprintw (0, startx, "%s", m_title.chars()); |
212 mvprintw(0, startx, "%s", m_title.chars()); |
213 mvhline (0, 0, ' ', startx); |
213 mvhline(0, 0, ' ', startx); |
214 mvhline (0, endx, ' ', COLS - endx); |
214 mvhline(0, endx, ' ', COLS - endx); |
215 attroff (pair); |
215 attroff(pair); |
216 } |
216 } |
217 |
217 |
218 m_needRefresh = true; |
218 m_needRefresh = true; |
219 } |
219 } |
220 |
220 |
221 // ------------------------------------------------------------------------------------------------- |
221 // ------------------------------------------------------------------------------------------------- |
222 // |
222 // |
223 void Interface::setTitle (const String& title) |
223 void Interface::setTitle(const String& title) |
224 { |
224 { |
225 m_title = title; |
225 m_title = title; |
226 renderTitlebar(); |
226 renderTitlebar(); |
227 } |
227 } |
228 |
228 |
229 // ------------------------------------------------------------------------------------------------- |
229 // ------------------------------------------------------------------------------------------------- |
230 // |
230 // |
231 void Interface::safeDisconnect (std::function<void(bool)> afterwards) |
231 void Interface::safeDisconnect(std::function<void(bool)> afterwards) |
232 { |
232 { |
233 if (m_session.is_active()) |
233 if (m_session.is_active()) |
234 { |
234 { |
235 m_disconnectCallback = afterwards; |
235 m_disconnectCallback = afterwards; |
236 setInputState (INPUTSTATE_CONFIRM_DISCONNECTION); |
236 setInputState(INPUTSTATE_CONFIRM_DISCONNECTION); |
237 } |
237 } |
238 else |
238 else |
239 afterwards(false); |
239 afterwards(false); |
240 } |
240 } |
241 |
241 |
242 // ------------------------------------------------------------------------------------------------- |
242 // ------------------------------------------------------------------------------------------------- |
243 // |
243 // |
244 int Interface::nicklistWidth() |
244 int Interface::nicklistWidth() |
245 { |
245 { |
246 // Allocate at least 12 characters, at most 24 characters, for the nicklist. If we cannot |
246 // Allocate at least 12 characters, at most 24 characters, for the nicklist. If we cannot |
247 // afford that (o_O) then we probably shouldn't draw the nicklist at all I think. |
247 // afford that(o_O) then we probably shouldn't draw the nicklist at all I think. |
248 int nicklistWidth = COLS / 4; |
248 int nicklistWidth = COLS / 4; |
249 |
249 |
250 if (nicklistWidth < 12) |
250 if (nicklistWidth < 12) |
251 return 0; |
251 return 0; |
252 |
252 |
253 return min (nicklistWidth, 24); |
253 return min(nicklistWidth, 24); |
254 } |
254 } |
255 |
255 |
256 // ------------------------------------------------------------------------------------------------- |
256 // ------------------------------------------------------------------------------------------------- |
257 // Renders the given colored line onto the screen. Will wrap if allowWrap is true. Returns the |
257 // Renders the given colored line onto the screen. Will wrap if allowWrap is true. Returns the |
258 // 'y' value for the next line. |
258 // 'y' value for the next line. |
259 // |
259 // |
260 int Interface::renderColorline (int y, int x0, int width, const ColoredLine& line, bool allowWrap) |
260 int Interface::renderColorline(int y, int x0, int width, const ColoredLine& line, bool allowWrap) |
261 { |
261 { |
262 int x = x0; |
262 int x = x0; |
263 |
263 |
264 for (int byte : line.data()) |
264 for (int byte : line.data()) |
265 { |
265 { |
355 m_outputScroll = m_outputLines.size() - 1 - end; |
355 m_outputScroll = m_outputLines.size() - 1 - end; |
356 |
356 |
357 if (start < 0 or start == end or printOffset >= height) |
357 if (start < 0 or start == end or printOffset >= height) |
358 return; |
358 return; |
359 |
359 |
360 assert (start <= end and start - end <= height); |
360 assert(start <= end and start - end <= height); |
361 |
361 |
362 // Clear the display |
362 // Clear the display |
363 for (int i : range(height)) |
363 for (int i : range(height)) |
364 mvhline (y + i, 0, ' ', width); |
364 mvhline(y + i, 0, ' ', width); |
365 |
365 |
366 // Print the lines |
366 // Print the lines |
367 y += printOffset; |
367 y += printOffset; |
368 |
368 |
369 for (int i : range(start, end)) |
369 for (int i : range(start, end)) |
370 y = renderColorline (y, 0, width, m_outputLines[i], true); |
370 y = renderColorline(y, 0, width, m_outputLines[i], true); |
371 |
371 |
372 m_needOutputRender = false; |
372 m_needOutputRender = false; |
373 m_needRefresh = true; |
373 m_needRefresh = true; |
374 } |
374 } |
375 |
375 |
401 |
401 |
402 // ------------------------------------------------------------------------------------------------- |
402 // ------------------------------------------------------------------------------------------------- |
403 // |
403 // |
404 void Interface::renderInput() |
404 void Interface::renderInput() |
405 { |
405 { |
406 chtype promptColor = getColorPair (WHITE, BLUE); |
406 chtype promptColor = getColorPair(WHITE, BLUE); |
407 |
407 |
408 // If we're asking the user if they want to disconnect, we don't render any input strings, |
408 // If we're asking the user if they want to disconnect, we don't render any input strings, |
409 // just the confirmation message. |
409 // just the confirmation message. |
410 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
410 if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION) |
411 { |
411 { |
412 attron (promptColor); |
412 attron(promptColor); |
413 mvhline (LINES - 2, 0, ' ', COLS); |
413 mvhline(LINES - 2, 0, ' ', COLS); |
414 mvprintw (LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
414 mvprintw(LINES - 2, 0, "Are you sure you want to disconnect? y/n"); |
415 attroff (promptColor); |
415 attroff(promptColor); |
416 m_needRefresh = true; |
416 m_needRefresh = true; |
417 return; |
417 return; |
418 } |
418 } |
419 |
419 |
420 String prompt = getPromptString(); |
420 String prompt = getPromptString(); |
428 for (char &ch : displayString) |
428 for (char &ch : displayString) |
429 ch = '*'; |
429 ch = '*'; |
430 } |
430 } |
431 |
431 |
432 // Ensure the cursor is within bounds |
432 // Ensure the cursor is within bounds |
433 m_cursorPosition = clamp (m_cursorPosition, 0, displayString.length()); |
433 m_cursorPosition = clamp(m_cursorPosition, 0, displayString.length()); |
434 |
434 |
435 // Ensure that the cursor is always in view, adjust panning if this is not the case |
435 // Ensure that the cursor is always in view, adjust panning if this is not the case |
436 if (m_cursorPosition > m_inputPanning + displayLength) |
436 if (m_cursorPosition > m_inputPanning + displayLength) |
437 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
437 m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right |
438 else if (m_cursorPosition < m_inputPanning) |
438 else if (m_cursorPosition < m_inputPanning) |
439 m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left |
439 m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left |
440 |
440 |
441 // What part of the string to draw? |
441 // What part of the string to draw? |
442 int start = m_inputPanning; |
442 int start = m_inputPanning; |
443 int end = min<int> (displayString.length(), start + displayLength); |
443 int end = min<int>(displayString.length(), start + displayLength); |
444 assert (m_cursorPosition >= start and m_cursorPosition <= end); |
444 assert(m_cursorPosition >= start and m_cursorPosition <= end); |
445 |
445 |
446 // Render the input string |
446 // Render the input string |
447 mvhline (LINES - 2, 0, ' ', COLS); |
447 mvhline(LINES - 2, 0, ' ', COLS); |
448 mvprintw (y, prompt.length() + 1, "%s", displayString.mid (start, end).chars()); |
448 mvprintw(y, prompt.length() + 1, "%s", displayString.mid(start, end).chars()); |
449 |
449 |
450 // Render the prompt |
450 // Render the prompt |
451 attron (promptColor); |
451 attron(promptColor); |
452 mvprintw (y, 0, "%s", prompt.chars()); |
452 mvprintw(y, 0, "%s", prompt.chars()); |
453 attroff (promptColor); |
453 attroff(promptColor); |
454 |
454 |
455 // Store in memory where the cursor is now (so that we can re-draw it to position the terminal |
455 // Store in memory where the cursor is now(so that we can re-draw it to position the terminal |
456 // cursor). |
456 // cursor). |
457 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
457 m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0'; |
458 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
458 m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning); |
459 m_needRefresh = true; |
459 m_needRefresh = true; |
460 m_needInputRender = false; |
460 m_needInputRender = false; |
462 |
462 |
463 // ------------------------------------------------------------------------------------------------- |
463 // ------------------------------------------------------------------------------------------------- |
464 // |
464 // |
465 void Interface::renderStatusBar() |
465 void Interface::renderStatusBar() |
466 { |
466 { |
467 chtype color = getColorPair (WHITE, BLUE); |
467 chtype color = getColorPair(WHITE, BLUE); |
468 int y = LINES - 1; |
468 int y = LINES - 1; |
469 attron (color); |
469 attron(color); |
470 mvhline (y, 0, ' ', COLS); |
470 mvhline(y, 0, ' ', COLS); |
471 mvprintw (y, 0, "%s", m_statusBarText.chars()); |
471 mvprintw(y, 0, "%s", m_statusBarText.chars()); |
472 attroff (color); |
472 attroff(color); |
473 m_needRefresh = true; |
473 m_needRefresh = true; |
474 m_needStatusBarRender = false; |
474 m_needStatusBarRender = false; |
475 } |
475 } |
476 |
476 |
477 // ------------------------------------------------------------------------------------------------- |
477 // ------------------------------------------------------------------------------------------------- |
499 { |
499 { |
500 adminText = "No other admins"; |
500 adminText = "No other admins"; |
501 } |
501 } |
502 else |
502 else |
503 { |
503 { |
504 adminText.sprintf ("%d other admin%s", m_session.num_admins(), |
504 adminText.sprintf("%d other admin%s", m_session.num_admins(), |
505 m_session.num_admins() != 1 ? "s" : ""); |
505 m_session.num_admins() != 1 ? "s" : ""); |
506 } |
506 } |
507 |
507 |
508 text.sprintf ("%s | %s | %s", |
508 text.sprintf("%s | %s | %s", |
509 m_session.address().to_string (IPAddress::WITH_PORT).chars(), |
509 m_session.address().to_string(IPAddress::WITH_PORT).chars(), |
510 m_session.level().chars(), |
510 m_session.level().chars(), |
511 adminText.chars()); |
511 adminText.chars()); |
512 } |
512 } |
513 break; |
513 break; |
514 } |
514 } |
515 |
515 |
516 if (not text.is_empty()) |
516 if (not text.is_empty()) |
517 text += " | "; |
517 text += " | "; |
518 |
518 |
519 text += "Ctrl+N to connect, Ctrl+Q to "; |
519 text += "Ctrl+N to connect, Ctrl+Q to "; |
520 text += (m_session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
520 text +=(m_session.state() == RCON_DISCONNECTED) ? "quit" : "disconnect"; |
521 |
521 |
522 if (text != m_statusBarText) |
522 if (text != m_statusBarText) |
523 { |
523 { |
524 m_statusBarText = text; |
524 m_statusBarText = text; |
525 m_needStatusBarRender = true; |
525 m_needStatusBarRender = true; |
547 return; |
547 return; |
548 |
548 |
549 int y = LINES - 2; |
549 int y = LINES - 2; |
550 |
550 |
551 if (m_cursorCharacter.ch != '\0') |
551 if (m_cursorCharacter.ch != '\0') |
552 mvprintw (y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
552 mvprintw(y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch); |
553 else |
553 else |
554 mvprintw (y, getPromptString().length(), " "); |
554 mvprintw(y, getPromptString().length(), " "); |
555 } |
555 } |
556 |
556 |
557 // ------------------------------------------------------------------------------------------------- |
557 // ------------------------------------------------------------------------------------------------- |
558 // |
558 // |
559 int Interface::findPreviousWord() |
559 int Interface::findPreviousWord() |
560 { |
560 { |
561 const String& input = getCurrentInput(); |
561 const String& input = getCurrentInput(); |
562 int pos = m_cursorPosition; |
562 int pos = m_cursorPosition; |
563 |
563 |
564 // Move past whitespace |
564 // Move past whitespace |
565 while (pos > 0 and isspace (input[pos - 1])) |
565 while (pos > 0 and isspace(input[pos - 1])) |
566 pos--; |
566 pos--; |
567 |
567 |
568 // Move past the word |
568 // Move past the word |
569 while (pos > 0 and not isspace (input[pos - 1])) |
569 while (pos > 0 and not isspace(input[pos - 1])) |
570 pos--; |
570 pos--; |
571 |
571 |
572 return pos; |
572 return pos; |
573 } |
573 } |
574 |
574 |
578 { |
578 { |
579 const String& input = getCurrentInput(); |
579 const String& input = getCurrentInput(); |
580 int pos = m_cursorPosition; |
580 int pos = m_cursorPosition; |
581 |
581 |
582 // Move past current whitespace |
582 // Move past current whitespace |
583 while (pos < input.length() and isspace (input[pos])) |
583 while (pos < input.length() and isspace(input[pos])) |
584 pos++; |
584 pos++; |
585 |
585 |
586 // Move past the word |
586 // Move past the word |
587 while (input[pos] != '\0' and not isspace (input[pos])) |
587 while (input[pos] != '\0' and not isspace(input[pos])) |
588 pos++; |
588 pos++; |
589 |
589 |
590 return pos; |
590 return pos; |
591 } |
591 } |
592 |
592 |
593 // ------------------------------------------------------------------------------------------------- |
593 // ------------------------------------------------------------------------------------------------- |
594 // |
594 // |
595 void Interface::yank (int a, int b) |
595 void Interface::yank(int a, int b) |
596 { |
596 { |
597 if (a >= b) |
597 if (a >= b) |
598 return; |
598 return; |
599 |
599 |
600 if (m_cursorPosition > a and m_cursorPosition <= b) |
600 if (m_cursorPosition > a and m_cursorPosition <= b) |
601 m_cursorPosition = a; |
601 m_cursorPosition = a; |
602 |
602 |
603 String& input = getEditableInput(); |
603 String& input = getEditableInput(); |
604 m_pasteBuffer = input.mid (a, b); |
604 m_pasteBuffer = input.mid(a, b); |
605 input.remove (a, b - a); |
605 input.remove(a, b - a); |
606 m_needInputRender = true; |
606 m_needInputRender = true; |
607 } |
607 } |
608 |
608 |
609 // ------------------------------------------------------------------------------------------------- |
609 // ------------------------------------------------------------------------------------------------- |
610 // |
610 // |
647 { |
647 { |
648 case INPUTSTATE_CONFIRM_DISCONNECTION: |
648 case INPUTSTATE_CONFIRM_DISCONNECTION: |
649 break; |
649 break; |
650 |
650 |
651 case INPUTSTATE_NORMAL: |
651 case INPUTSTATE_NORMAL: |
652 safeDisconnect ([&](bool hadsession) |
652 safeDisconnect([&](bool hadsession) |
653 { |
653 { |
654 if (hadsession) |
654 if (hadsession) |
655 { |
655 { |
656 setInputState (INPUTSTATE_NORMAL); |
656 setInputState(INPUTSTATE_NORMAL); |
657 } |
657 } |
658 else |
658 else |
659 { |
659 { |
660 endwin(); |
660 endwin(); |
661 throw Exitception(); |
661 throw Exitception(); |
662 } |
662 } |
663 }); |
663 }); |
664 break; |
664 break; |
665 |
665 |
666 case INPUTSTATE_PASSWORD: |
666 case INPUTSTATE_PASSWORD: |
667 setInputState (INPUTSTATE_ADDRESS); |
667 setInputState(INPUTSTATE_ADDRESS); |
668 break; |
668 break; |
669 |
669 |
670 case INPUTSTATE_ADDRESS: |
670 case INPUTSTATE_ADDRESS: |
671 setInputState (INPUTSTATE_NORMAL); |
671 setInputState(INPUTSTATE_NORMAL); |
672 } |
672 } |
673 break; |
673 break; |
674 |
674 |
675 case KEY_LEFT: |
675 case KEY_LEFT: |
676 case 'B' - 'A' + 1: // readline ^B |
676 case 'B' - 'A' + 1: // readline ^B |
715 |
715 |
716 case KEY_BACKSPACE: |
716 case KEY_BACKSPACE: |
717 case '\b': |
717 case '\b': |
718 if (m_cursorPosition > 0) |
718 if (m_cursorPosition > 0) |
719 { |
719 { |
720 getEditableInput().remove_at (--m_cursorPosition); |
720 getEditableInput().remove_at(--m_cursorPosition); |
721 m_needInputRender = true; |
721 m_needInputRender = true; |
722 } |
722 } |
723 break; |
723 break; |
724 |
724 |
725 case KEY_DC: |
725 case KEY_DC: |
726 case 'D' - 'A' + 1: // readline ^D |
726 case 'D' - 'A' + 1: // readline ^D |
727 if (m_cursorPosition < getCurrentInput().length()) |
727 if (m_cursorPosition < getCurrentInput().length()) |
728 { |
728 { |
729 getEditableInput().remove_at (m_cursorPosition); |
729 getEditableInput().remove_at(m_cursorPosition); |
730 m_needInputRender = true; |
730 m_needInputRender = true; |
731 } |
731 } |
732 break; |
732 break; |
733 |
733 |
734 case KEY_PPAGE: |
734 case KEY_PPAGE: |
735 m_outputScroll += min (PAGE_SIZE, LINES / 2); |
735 m_outputScroll += min(PAGE_SIZE, LINES / 2); |
736 m_needOutputRender = true; |
736 m_needOutputRender = true; |
737 break; |
737 break; |
738 |
738 |
739 case KEY_NPAGE: |
739 case KEY_NPAGE: |
740 m_outputScroll -= min (PAGE_SIZE, LINES / 2); |
740 m_outputScroll -= min(PAGE_SIZE, LINES / 2); |
741 m_needOutputRender = true; |
741 m_needOutputRender = true; |
742 break; |
742 break; |
743 |
743 |
744 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
744 case 'U' - 'A' + 1: // readline ^U - delete from start to cursor |
745 if (m_cursorPosition > 0) |
745 if (m_cursorPosition > 0) |
746 { |
746 { |
747 yank (0, m_cursorPosition); |
747 yank(0, m_cursorPosition); |
748 m_cursorPosition = 0; |
748 m_cursorPosition = 0; |
749 } |
749 } |
750 break; |
750 break; |
751 |
751 |
752 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
752 case 'K' - 'A' + 1: // readline ^K - delete from cursor to end |
753 yank (m_cursorPosition, getEditableInput().length()); |
753 yank(m_cursorPosition, getEditableInput().length()); |
754 break; |
754 break; |
755 |
755 |
756 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
756 case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current |
757 yank (findPreviousWord(), m_cursorPosition); |
757 yank(findPreviousWord(), m_cursorPosition); |
758 break; |
758 break; |
759 |
759 |
760 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
760 case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text |
761 if (not m_pasteBuffer.is_empty()) |
761 if (not m_pasteBuffer.is_empty()) |
762 { |
762 { |
763 getEditableInput().insert (m_cursorPosition, m_pasteBuffer); |
763 getEditableInput().insert(m_cursorPosition, m_pasteBuffer); |
764 m_cursorPosition += m_pasteBuffer.length(); |
764 m_cursorPosition += m_pasteBuffer.length(); |
765 m_needInputRender = true; |
765 m_needInputRender = true; |
766 } |
766 } |
767 break; |
767 break; |
768 |
768 |
769 case '\t': |
769 case '\t': |
770 { |
770 { |
771 int space = getCurrentInput().find (" "); |
771 int space = getCurrentInput().find(" "); |
772 |
772 |
773 if (m_inputState == INPUTSTATE_NORMAL |
773 if (m_inputState == INPUTSTATE_NORMAL |
774 and m_cursorPosition > 0 |
774 and m_cursorPosition > 0 |
775 and (space == -1 or space >= m_cursorPosition)) |
775 and(space == -1 or space >= m_cursorPosition)) |
776 { |
776 { |
777 String start = getCurrentInput().mid (0, m_cursorPosition); |
777 String start = getCurrentInput().mid(0, m_cursorPosition); |
778 m_session.request_tab_complete (start); |
778 m_session.request_tab_complete(start); |
779 } |
779 } |
780 } |
780 } |
781 break; |
781 break; |
782 |
782 |
783 case '\n': |
783 case '\n': |
789 break; // handled above |
789 break; // handled above |
790 |
790 |
791 case INPUTSTATE_ADDRESS: |
791 case INPUTSTATE_ADDRESS: |
792 try |
792 try |
793 { |
793 { |
794 m_remoteAddress = IPAddress::from_string (getCurrentInput()); |
794 m_remoteAddress = IPAddress::from_string(getCurrentInput()); |
795 } |
795 } |
796 catch (std::exception& e) |
796 catch (std::exception& e) |
797 { |
797 { |
798 print ("%s\n", e.what()); |
798 print("%s\n", e.what()); |
799 return; |
799 return; |
800 } |
800 } |
801 |
801 |
802 if (m_remoteAddress.port == 0) |
802 if (m_remoteAddress.port == 0) |
803 m_remoteAddress.port = 10666; |
803 m_remoteAddress.port = 10666; |
804 |
804 |
805 setInputState (INPUTSTATE_PASSWORD); |
805 setInputState(INPUTSTATE_PASSWORD); |
806 break; |
806 break; |
807 |
807 |
808 case INPUTSTATE_PASSWORD: |
808 case INPUTSTATE_PASSWORD: |
809 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().is_empty()) |
809 if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().is_empty()) |
810 { |
810 { |
811 m_session.disconnect(); |
811 m_session.disconnect(); |
812 m_session.set_password (getCurrentInput()); |
812 m_session.set_password(getCurrentInput()); |
813 m_session.connect (m_remoteAddress); |
813 m_session.connect(m_remoteAddress); |
814 setInputState (INPUTSTATE_NORMAL); |
814 setInputState(INPUTSTATE_NORMAL); |
815 } |
815 } |
816 break; |
816 break; |
817 |
817 |
818 case INPUTSTATE_NORMAL: |
818 case INPUTSTATE_NORMAL: |
819 if (getCurrentInput()[0] == '/') |
819 if (getCurrentInput()[0] == '/') |
820 { |
820 { |
821 handleCommand(getCurrentInput()); |
821 handleCommand(getCurrentInput()); |
822 flushInput(); |
822 flushInput(); |
823 } |
823 } |
824 else if (m_session.send_command (getCurrentInput())) |
824 else if (m_session.send_command(getCurrentInput())) |
825 { |
825 { |
826 flushInput(); |
826 flushInput(); |
827 } |
827 } |
828 break; |
828 break; |
829 } |
829 } |
830 break; |
830 break; |
831 |
831 |
832 case 'N' - 'A' + 1: // ^N |
832 case 'N' - 'A' + 1: // ^N |
833 if (m_inputState == INPUTSTATE_NORMAL) |
833 if (m_inputState == INPUTSTATE_NORMAL) |
834 safeDisconnect ([&](bool){setInputState (INPUTSTATE_ADDRESS);}); |
834 safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);}); |
835 break; |
835 break; |
836 |
836 |
837 case '\x1b': // Escape |
837 case '\x1b': // Escape |
838 // We may have an alt key coming |
838 // We may have an alt key coming |
839 ch = ::getch(); |
839 ch = ::getch(); |
857 break; |
857 break; |
858 |
858 |
859 case 'd': |
859 case 'd': |
860 case 'D': |
860 case 'D': |
861 // readline alt-d - delete from here till next word boundary |
861 // readline alt-d - delete from here till next word boundary |
862 yank (m_cursorPosition, findNextWord()); |
862 yank(m_cursorPosition, findNextWord()); |
863 break; |
863 break; |
864 |
864 |
865 case KEY_BACKSPACE: // alt+backspace, remove previous word |
865 case KEY_BACKSPACE: // alt+backspace, remove previous word |
866 case '\b': |
866 case '\b': |
867 yank (findPreviousWord(), m_cursorPosition); |
867 yank(findPreviousWord(), m_cursorPosition); |
868 break; |
868 break; |
869 } |
869 } |
870 } |
870 } |
871 else |
871 else |
872 { |
872 { |
873 // No alt-key, handle pure escape |
873 // No alt-key, handle pure escape |
874 if (m_inputState == INPUTSTATE_PASSWORD) |
874 if (m_inputState == INPUTSTATE_PASSWORD) |
875 setInputState (INPUTSTATE_ADDRESS); |
875 setInputState(INPUTSTATE_ADDRESS); |
876 else if (m_inputState == INPUTSTATE_ADDRESS) |
876 else if (m_inputState == INPUTSTATE_ADDRESS) |
877 setInputState (INPUTSTATE_NORMAL); |
877 setInputState(INPUTSTATE_NORMAL); |
878 } |
878 } |
879 break; |
879 break; |
880 } |
880 } |
881 |
881 |
882 render(); |
882 render(); |
899 } |
899 } |
900 } |
900 } |
901 |
901 |
902 // ------------------------------------------------------------------------------------------------- |
902 // ------------------------------------------------------------------------------------------------- |
903 // |
903 // |
904 void Interface::vprint (const char* fmtstr, va_list args) |
904 void Interface::vprint(const char* fmtstr, va_list args) |
905 { |
905 { |
906 String message; |
906 String message; |
907 message.vsprintf (fmtstr, args); |
907 message.vsprintf(fmtstr, args); |
908 printToConsole (message); |
908 printToConsole(message); |
909 } |
909 } |
910 |
910 |
911 // ------------------------------------------------------------------------------------------------- |
911 // ------------------------------------------------------------------------------------------------- |
912 // |
912 // |
913 void __cdecl Interface::printText (const char* fmtstr, ...) |
913 void __cdecl Interface::printText(const char* fmtstr, ...) |
914 { |
914 { |
915 va_list args; |
915 va_list args; |
916 va_start (args, fmtstr); |
916 va_start(args, fmtstr); |
917 vprint (fmtstr, args); |
917 vprint(fmtstr, args); |
918 va_end (args); |
918 va_end(args); |
919 } |
919 } |
920 |
920 |
921 // ------------------------------------------------------------------------------------------------- |
921 // ------------------------------------------------------------------------------------------------- |
922 // |
922 // |
923 void __cdecl Interface::print (const char* fmtstr, ...) |
923 void __cdecl Interface::print(const char* fmtstr, ...) |
924 { |
924 { |
925 va_list args; |
925 va_list args; |
926 va_start (args, fmtstr); |
926 va_start(args, fmtstr); |
927 printToConsole (TEXTCOLOR_BrightBlue); |
927 printToConsole(TEXTCOLOR_BrightBlue); |
928 vprint (fmtstr, args); |
928 vprint(fmtstr, args); |
929 va_end (args); |
929 va_end(args); |
930 } |
930 } |
931 |
931 |
932 // ------------------------------------------------------------------------------------------------- |
932 // ------------------------------------------------------------------------------------------------- |
933 // |
933 // |
934 void __cdecl Interface::printWarning (const char* fmtstr, ...) |
934 void __cdecl Interface::printWarning(const char* fmtstr, ...) |
935 { |
935 { |
936 va_list args; |
936 va_list args; |
937 va_start (args, fmtstr); |
937 va_start(args, fmtstr); |
938 printToConsole (TEXTCOLOR_BrightYellow "-!- "); |
938 printToConsole(TEXTCOLOR_BrightYellow "-!- "); |
939 vprint (fmtstr, args); |
939 vprint(fmtstr, args); |
940 va_end (args); |
940 va_end(args); |
941 } |
941 } |
942 |
942 |
943 // ------------------------------------------------------------------------------------------------- |
943 // ------------------------------------------------------------------------------------------------- |
944 // |
944 // |
945 void __cdecl Interface::printError (const char* fmtstr, ...) |
945 void __cdecl Interface::printError(const char* fmtstr, ...) |
946 { |
946 { |
947 va_list args; |
947 va_list args; |
948 va_start (args, fmtstr); |
948 va_start(args, fmtstr); |
949 printToConsole (TEXTCOLOR_BrightRed "!!! "); |
949 printToConsole(TEXTCOLOR_BrightRed "!!! "); |
950 vprint (fmtstr, args); |
950 vprint(fmtstr, args); |
951 va_end (args); |
951 va_end(args); |
952 } |
952 } |
953 |
953 |
954 // ------------------------------------------------------------------------------------------------- |
954 // ------------------------------------------------------------------------------------------------- |
955 // |
955 // |
956 void Interface::printToConsole (String message) |
956 void Interface::printToConsole(String message) |
957 { |
957 { |
958 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
958 // Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C". |
959 // Let's correct that on our end and hope this won't cause conflicts. |
959 // Let's correct that on our end and hope this won't cause conflicts. |
960 message.replace ("\\c", "\x1C"); |
960 message.replace("\\c", "\x1C"); |
961 |
961 |
962 for (char ch : message) |
962 for (char ch : message) |
963 { |
963 { |
964 if (ch == '\n') |
964 if (ch == '\n') |
965 { |
965 { |
969 } |
969 } |
970 |
970 |
971 if (m_outputLines.last().length() == 0) |
971 if (m_outputLines.last().length() == 0) |
972 { |
972 { |
973 time_t now; |
973 time_t now; |
974 time (&now); |
974 time(&now); |
975 char timestamp[32]; |
975 char timestamp[32]; |
976 strftime (timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime (&now)); |
976 strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now)); |
977 |
977 |
978 for (char ch : String(timestamp)) |
978 for (char ch : String(timestamp)) |
979 m_outputLines.last().add_char (ch); |
979 m_outputLines.last().add_char(ch); |
980 } |
980 } |
981 |
981 |
982 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
982 // Remove some lines if there's too many of them. 20,000 should be enough, I hope. |
983 while (m_outputLines.size() > 20000) |
983 while (m_outputLines.size() > 20000) |
984 m_outputLines.remove_at(0); |
984 m_outputLines.remove_at(0); |
985 |
985 |
986 m_outputLines.last().add_char (ch); |
986 m_outputLines.last().add_char(ch); |
987 } |
987 } |
988 |
988 |
989 m_needOutputRender = true; |
989 m_needOutputRender = true; |
990 } |
990 } |
991 |
991 |
992 // ------------------------------------------------------------------------------------------------- |
992 // ------------------------------------------------------------------------------------------------- |
993 // |
993 // |
994 void Interface::connect (String address, String password) |
994 void Interface::connect(String address, String password) |
995 { |
995 { |
996 try |
996 try |
997 { |
997 { |
998 m_remoteAddress = IPAddress::from_string (address); |
998 m_remoteAddress = IPAddress::from_string(address); |
999 } |
999 } |
1000 catch (std::exception& e) |
1000 catch (std::exception& e) |
1001 { |
1001 { |
1002 print ("%s\n", e.what()); |
1002 print("%s\n", e.what()); |
1003 return; |
1003 return; |
1004 } |
1004 } |
1005 |
1005 |
1006 if (m_remoteAddress.port == 0) |
1006 if (m_remoteAddress.port == 0) |
1007 m_remoteAddress.port = 10666; |
1007 m_remoteAddress.port = 10666; |
1008 |
1008 |
1009 m_session.disconnect(); |
1009 m_session.disconnect(); |
1010 m_session.set_password (password); |
1010 m_session.set_password(password); |
1011 m_session.connect (m_remoteAddress); |
1011 m_session.connect(m_remoteAddress); |
1012 } |
1012 } |
1013 |
1013 |
1014 // ------------------------------------------------------------------------------------------------- |
1014 // ------------------------------------------------------------------------------------------------- |
1015 // |
1015 // |
1016 void Interface::setPlayerNames (const StringList& names) |
1016 void Interface::setPlayerNames(const StringList& names) |
1017 { |
1017 { |
1018 m_playerNames.clear(); |
1018 m_playerNames.clear(); |
1019 |
1019 |
1020 for (const String& name : names) |
1020 for (const String& name : names) |
1021 { |
1021 { |
1022 ColoredLine coloredname; |
1022 ColoredLine coloredname; |
1023 coloredname.add_string (name); |
1023 coloredname.add_string(name); |
1024 coloredname.finalize(); |
1024 coloredname.finalize(); |
1025 m_playerNames.append (coloredname); |
1025 m_playerNames.append(coloredname); |
1026 } |
1026 } |
1027 |
1027 |
1028 m_needNicklistRender = true; |
1028 m_needNicklistRender = true; |
1029 } |
1029 } |
1030 |
1030 |
1031 // ------------------------------------------------------------------------------------------------- |
1031 // ------------------------------------------------------------------------------------------------- |
1032 // |
1032 // |
1033 void Interface::tabComplete (const String& part, String complete) |
1033 void Interface::tabComplete(const String& part, String complete) |
1034 { |
1034 { |
1035 String& input = getEditableInput(); |
1035 String& input = getEditableInput(); |
1036 |
1036 |
1037 if (input.starts_with (part)) |
1037 if (input.starts_with(part)) |
1038 { |
1038 { |
1039 if (input[part.length()] != ' ') |
1039 if (input[part.length()] != ' ') |
1040 complete += ' '; |
1040 complete += ' '; |
1041 |
1041 |
1042 input.replace (0, part.length(), complete); |
1042 input.replace(0, part.length(), complete); |
1043 m_cursorPosition = complete.length(); |
1043 m_cursorPosition = complete.length(); |
1044 m_needInputRender = true; |
1044 m_needInputRender = true; |
1045 } |
1045 } |
1046 } |
1046 } |
1047 |
1047 |
1109 |
1109 |
1110 // ------------------------------------------------------------------------------------------------- |
1110 // ------------------------------------------------------------------------------------------------- |
1111 // |
1111 // |
1112 void Interface::resetTitle() |
1112 void Interface::resetTitle() |
1113 { |
1113 { |
1114 m_title.sprintf ("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1114 m_title.sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string()); |
1115 } |
1115 } |
1116 |
1116 |
1117 // ------------------------------------------------------------------------------------------------- |
1117 // ------------------------------------------------------------------------------------------------- |
1118 // |
1118 // |
1119 void Interface::flushInput() |
1119 void Interface::flushInput() |
1120 { |
1120 { |
1121 m_inputHistory.insert (0, ""); |
1121 m_inputHistory.insert(0, ""); |
1122 m_inputCursor = 0; |
1122 m_inputCursor = 0; |
1123 m_needInputRender = true; |
1123 m_needInputRender = true; |
1124 } |
1124 } |
1125 |
1125 |
1126 END_ZFC_NAMESPACE |
1126 END_ZFC_NAMESPACE |