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 // ------------------------------------------------------------------------------------------------- |
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); |
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()) |
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; |
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 |