/[LeafOK_CVS]/lbbs/src/screen.c
ViewVC logotype

Contents of /lbbs/src/screen.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.105 - (show annotations)
Wed Jul 2 06:25:02 2025 UTC (8 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.104: +9 -8 lines
Content type: text/x-csrc
Fix bug in calculating space length for UTF8 string

1 /***************************************************************************
2 screen.c - description
3 -------------------
4 Copyright : (C) 2004-2025 by Leaflet
5 Email : leaflet@leafok.com
6 ***************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 3 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "bbs.h"
18 #include "common.h"
19 #include "editor.h"
20 #include "file_loader.h"
21 #include "io.h"
22 #include "log.h"
23 #include "login.h"
24 #include "screen.h"
25 #include "str_process.h"
26 #include <ctype.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <sys/param.h>
33 #include <sys/stat.h>
34 #include <sys/shm.h>
35 #include <sys/types.h>
36
37 #define ACTIVE_BOARD_HEIGHT 8
38
39 #define STR_TOP_LEFT_MAX_LEN 80
40 #define STR_TOP_MIDDLE_MAX_LEN 40
41 #define STR_TOP_RIGHT_MAX_LEN 40
42
43 void moveto(int row, int col)
44 {
45 if (row >= 0)
46 {
47 prints("\033[%d;%dH", row, col);
48 }
49 else
50 {
51 prints("\r");
52 }
53 }
54
55 void clrtoeol()
56 {
57 prints(CTRL_SEQ_CLR_LINE);
58 }
59
60 void clrline(int line_begin, int line_end)
61 {
62 int i;
63
64 for (i = line_begin; i <= line_end; i++)
65 {
66 moveto(i, 0);
67 prints(CTRL_SEQ_CLR_LINE);
68 }
69 }
70
71 void clrtobot(int line_begin)
72 {
73 moveto(line_begin, 0);
74 prints("\033[J");
75 moveto(line_begin, 0);
76 }
77
78 void clearscr()
79 {
80 prints("\033[2J");
81 moveto(0, 0);
82 }
83
84 int press_any_key()
85 {
86 moveto(SCREEN_ROWS, 0);
87 clrtoeol();
88
89 prints(" \033[1;33m按任意键继续...\033[0;37m");
90 iflush();
91
92 return igetch_t(MIN(MAX_DELAY_TIME, 60));
93 }
94
95 void set_input_echo(int echo)
96 {
97 if (echo)
98 {
99 outc('\x83'); // ASCII code 131
100 iflush();
101 }
102 else
103 {
104 // outc ('\x85'); // ASCII code 133
105 prints("\xff\xfb\x01\xff\xfb\x03");
106 iflush();
107 igetch(0);
108 igetch_reset();
109 }
110 }
111
112 static int _str_input(char *buffer, int buf_size, int max_display_len, int echo_mode)
113 {
114 int ch;
115 int offset = 0;
116 int eol;
117 int display_len;
118 char input_str[4];
119 int str_len = 0;
120 char c;
121
122 buffer[buf_size - 1] = '\0';
123 offset = split_line(buffer, max_display_len, &eol, &display_len, 0);
124
125 igetch_reset();
126
127 while (!SYS_server_exit)
128 {
129 ch = igetch_t(MIN(MAX_DELAY_TIME, 60));
130
131 if (ch == CR)
132 {
133 igetch_reset();
134 break;
135 }
136 else if (ch == KEY_TIMEOUT || ch == KEY_NULL) // timeout or broken pipe
137 {
138 return -1;
139 }
140 else if (ch == LF || ch == '\0')
141 {
142 continue;
143 }
144 else if (ch == BACKSPACE)
145 {
146 if (offset > 0)
147 {
148 offset--;
149 if (buffer[offset] < 0 || buffer[offset] > 127) // UTF8
150 {
151 while (offset > 0 && (buffer[offset] & 0b11000000) != 0b11000000)
152 {
153 offset--;
154 }
155 display_len--;
156 prints("\033[D \033[D");
157 }
158 buffer[offset] = '\0';
159 display_len--;
160 prints("\033[D \033[D");
161 iflush();
162 }
163 continue;
164 }
165 else if (ch > 255 || iscntrl(ch))
166 {
167 continue;
168 }
169 else if ((ch & 0xff80) == 0x80) // head of multi-byte character
170 {
171 str_len = 0;
172 c = (char)(ch & 0b11110000);
173 while (c & 0b10000000)
174 {
175 input_str[str_len] = (char)(ch - 256);
176 str_len++;
177 c = (c & 0b01111111) << 1;
178
179 if ((c & 0b10000000) == 0) // Input completed
180 {
181 break;
182 }
183
184 // Expect additional bytes of input
185 ch = igetch(100); // 0.1 second
186 if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Ignore received bytes if no futher input
187 {
188 log_error("Ignore %d bytes of incomplete UTF8 character\n", str_len);
189 str_len = 0;
190 break;
191 }
192 }
193
194 if (str_len == 0) // Incomplete input
195 {
196 continue;
197 }
198
199 if (offset + str_len > buf_size - 1 || display_len + 2 > max_display_len) // No enough space for Chinese character
200 {
201 outc('\a');
202 iflush();
203 continue;
204 }
205
206 memcpy(buffer + offset, input_str, (size_t)str_len);
207 offset += str_len;
208 buffer[offset] = '\0';
209 display_len += 2;
210
211 switch (echo_mode)
212 {
213 case DOECHO:
214 prints(input_str);
215 break;
216 case NOECHO:
217 prints("**");
218 break;
219 }
220 }
221 else if (ch >= 32 && ch < 127) // Printable character
222 {
223 if (offset + 1 > buf_size - 1 || display_len + 1 > max_display_len)
224 {
225 outc('\a');
226 iflush();
227 continue;
228 }
229
230 buffer[offset++] = (char)ch;
231 buffer[offset] = '\0';
232 display_len++;
233
234 switch (echo_mode)
235 {
236 case DOECHO:
237 outc((char)ch);
238 break;
239 case NOECHO:
240 outc('*');
241 break;
242 }
243 }
244 else // Invalid character
245 {
246 continue;
247 }
248
249 iflush();
250 }
251
252 return offset;
253 }
254
255 int str_input(char *buffer, int buf_size, int echo_mode)
256 {
257 int len;
258
259 buffer[0] = '\0';
260
261 len = _str_input(buffer, buf_size, buf_size, echo_mode);
262
263 prints("\r\n");
264 iflush();
265
266 return len;
267 };
268
269 int get_data(int row, int col, char *prompt, char *buffer, int buf_size, int max_display_len, int echo_mode)
270 {
271 int len;
272
273 moveto(row, col);
274 prints("%s", prompt);
275 prints("%s", buffer);
276 iflush();
277
278 len = _str_input(buffer, buf_size, max_display_len, echo_mode);
279
280 return len;
281 }
282
283 int display_data(const void *p_data, long display_line_total, const long *p_line_offsets, int eof_exit,
284 display_data_key_handler key_handler, const char *help_filename)
285 {
286 static int show_help = 1;
287 char buffer[LINE_BUFFER_LEN];
288 DISPLAY_CTX ctx;
289 int ch = 0;
290 int input_ok;
291 const int screen_begin_row = 1;
292 const int screen_row_total = SCREEN_ROWS - screen_begin_row;
293 int output_current_row = screen_begin_row;
294 int output_end_row = SCREEN_ROWS - 1;
295 long int line_current = 0;
296 long int len;
297 long int percentile;
298 int loop;
299 int eol, display_len;
300
301 clrline(output_current_row, SCREEN_ROWS);
302
303 // update msg_ext with extended key handler
304 if (key_handler(&ch, &ctx) != 0)
305 {
306 return ch;
307 }
308
309 loop = 1;
310 while (!SYS_server_exit && loop)
311 {
312 if (eof_exit > 0 && line_current >= display_line_total && display_line_total <= screen_row_total)
313 {
314 if (eof_exit == 1)
315 {
316 ch = press_any_key();
317 }
318 else // if (eof_exit == 2)
319 {
320 iflush();
321 }
322
323 loop = 0;
324 break;
325 }
326
327 if (line_current >= display_line_total || output_current_row > output_end_row)
328 {
329 ctx.reach_begin = (line_current < output_current_row ? 1 : 0);
330
331 if (line_current - (output_current_row - screen_begin_row) + screen_row_total < display_line_total)
332 {
333 percentile = (line_current - (output_current_row - screen_begin_row) + screen_row_total) * 100 / display_line_total;
334 ctx.reach_end = 0;
335 }
336 else
337 {
338 percentile = 100;
339 ctx.reach_end = 1;
340 }
341
342 ctx.line_top = line_current - (output_current_row - screen_begin_row) + 1;
343 ctx.line_bottom = MIN(line_current - (output_current_row - screen_begin_row) + screen_row_total, display_line_total);
344
345 snprintf(buffer, sizeof(buffer),
346 "\033[1;44;33m第\033[32m%ld\033[33m-\033[32m%ld\033[33m行 (\033[32m%ld%%\033[33m) %s",
347 ctx.line_top,
348 ctx.line_bottom,
349 percentile,
350 ctx.msg);
351
352 len = split_line(buffer, SCREEN_COLS, &eol, &display_len, 1);
353 for (; display_len < SCREEN_COLS; display_len++)
354 {
355 buffer[len++] = ' ';
356 }
357 buffer[len] = '\0';
358 strncat(buffer, "\033[m", sizeof(buffer) - 1 - strnlen(buffer, sizeof(buffer)));
359
360 moveto(SCREEN_ROWS, 0);
361 prints("%s", buffer);
362 iflush();
363
364 input_ok = 0;
365 while (!SYS_server_exit && !input_ok)
366 {
367 ch = igetch_t(MAX_DELAY_TIME);
368 input_ok = 1;
369
370 // extended key handler
371 if (key_handler(&ch, &ctx) != 0)
372 {
373 goto cleanup;
374 }
375
376 switch (ch)
377 {
378 case KEY_NULL:
379 case KEY_TIMEOUT:
380 goto cleanup;
381 case KEY_HOME:
382 if (line_current - output_current_row < 0) // Reach begin
383 {
384 break;
385 }
386 line_current = 0;
387 output_current_row = screen_begin_row;
388 output_end_row = SCREEN_ROWS - 1;
389 clrline(output_current_row, SCREEN_ROWS);
390 break;
391 case KEY_END:
392 if (display_line_total < screen_row_total)
393 {
394 break;
395 }
396 line_current = display_line_total - screen_row_total;
397 output_current_row = screen_begin_row;
398 output_end_row = SCREEN_ROWS - 1;
399 clrline(output_current_row, SCREEN_ROWS);
400 break;
401 case KEY_UP:
402 if (line_current - output_current_row < 0) // Reach begin
403 {
404 break;
405 }
406 line_current -= output_current_row;
407 output_current_row = screen_begin_row;
408 // screen_end_line = screen_begin_line;
409 // prints("\033[T"); // Scroll down 1 line
410 output_end_row = SCREEN_ROWS - 1; // Legacy Fterm only works with this line
411 break;
412 case CR:
413 igetch_reset();
414 case KEY_SPACE:
415 case KEY_DOWN:
416 if (line_current + (screen_row_total - (output_current_row - screen_begin_row)) >= display_line_total) // Reach end
417 {
418 break;
419 }
420 line_current += (screen_row_total - (output_current_row - screen_begin_row));
421 output_current_row = screen_row_total;
422 output_end_row = SCREEN_ROWS - 1;
423 moveto(SCREEN_ROWS, 0);
424 clrtoeol();
425 // prints("\033[S"); // Scroll up 1 line
426 prints("\n"); // Legacy Cterm only works with this line
427 break;
428 case KEY_PGUP:
429 if (line_current - output_current_row < 0) // Reach begin
430 {
431 break;
432 }
433 line_current -= ((screen_row_total - 1) + (output_current_row - screen_begin_row));
434 if (line_current < 0)
435 {
436 line_current = 0;
437 }
438 output_current_row = screen_begin_row;
439 output_end_row = SCREEN_ROWS - 1;
440 clrline(output_current_row, SCREEN_ROWS);
441 break;
442 case KEY_PGDN:
443 if (line_current + screen_row_total - (output_current_row - screen_begin_row) >= display_line_total) // Reach end
444 {
445 break;
446 }
447 line_current += (screen_row_total - 1) - (output_current_row - screen_begin_row);
448 if (line_current + screen_row_total > display_line_total) // No enough lines to display
449 {
450 line_current = display_line_total - screen_row_total;
451 }
452 output_current_row = screen_begin_row;
453 output_end_row = SCREEN_ROWS - 1;
454 clrline(output_current_row, SCREEN_ROWS);
455 break;
456 case KEY_ESC:
457 case KEY_LEFT:
458 loop = 0;
459 break;
460 case 'h':
461 if (!show_help) // Not reentrant
462 {
463 break;
464 }
465 // Display help information
466 show_help = 0;
467 display_file(help_filename, 1);
468 show_help = 1;
469 case KEY_F5:
470 // Refresh after display help information
471 line_current -= (output_current_row - screen_begin_row);
472 output_current_row = screen_begin_row;
473 output_end_row = SCREEN_ROWS - 1;
474 clrline(output_current_row, SCREEN_ROWS);
475 break;
476 case 0: // Refresh bottom line
477 break;
478 default:
479 input_ok = 0;
480 break;
481 }
482
483 BBS_last_access_tm = time(NULL);
484 }
485
486 continue;
487 }
488
489 len = p_line_offsets[line_current + 1] - p_line_offsets[line_current];
490 if (len >= sizeof(buffer))
491 {
492 log_error("Buffer overflow: len=%ld(%ld - %ld) line=%ld \n",
493 len, p_line_offsets[line_current + 1], p_line_offsets[line_current], line_current);
494 len = sizeof(buffer) - 1;
495 }
496 else if (len < 0)
497 {
498 log_error("Incorrect line offsets: len=%ld(%ld - %ld) line=%ld \n",
499 len, p_line_offsets[line_current + 1], p_line_offsets[line_current], line_current);
500 len = 0;
501 }
502
503 memcpy(buffer, (const char *)p_data + p_line_offsets[line_current], (size_t)len);
504 buffer[len] = '\0';
505
506 moveto(output_current_row, 0);
507 clrtoeol();
508 prints("%s", buffer);
509 line_current++;
510 output_current_row++;
511 }
512
513 cleanup:
514 return ch;
515 }
516
517 static int display_file_key_handler(int *p_key, DISPLAY_CTX *p_ctx)
518 {
519 switch (*p_key)
520 {
521 case 0: // Set msg
522 snprintf(p_ctx->msg, sizeof(p_ctx->msg),
523 "| 返回[\033[32m←\033[33m,\033[32mESC\033[33m] | "
524 "移动[\033[32m↑\033[33m/\033[32m↓\033[33m/\033[32mPgUp\033[33m/\033[32mPgDn\033[33m] | "
525 "帮助[\033[32mh\033[33m] |");
526 break;
527 }
528
529 return 0;
530 }
531
532 int display_file(const char *filename, int eof_exit)
533 {
534 int ret;
535 const void *p_shm;
536 size_t data_len;
537 long line_total;
538 const void *p_data;
539 const long *p_line_offsets;
540
541 if ((p_shm = get_file_shm_readonly(filename, &data_len, &line_total, &p_data, &p_line_offsets)) == NULL)
542 {
543 log_error("get_file_shm(%s) error\n", filename);
544 return KEY_NULL;
545 }
546
547 if (user_online_update("VIEW_FILE") < 0)
548 {
549 log_error("user_online_update(VIEW_FILE) error\n");
550 }
551
552 ret = display_data(p_data, line_total, p_line_offsets, eof_exit, display_file_key_handler, DATA_READ_HELP);
553
554 if (detach_file_shm(p_shm) < 0)
555 {
556 log_error("detach_file_shm(%s) error\n", filename);
557 }
558
559 return ret;
560 }
561
562 int show_top(const char *str_left, const char *str_middle, const char *str_right)
563 {
564 char str_left_f[STR_TOP_LEFT_MAX_LEN + 1];
565 char str_middle_f[STR_TOP_MIDDLE_MAX_LEN + 1];
566 char str_right_f[STR_TOP_RIGHT_MAX_LEN + 1];
567 int str_left_len;
568 int str_middle_len;
569 int str_right_len;
570 int eol;
571 int len;
572
573 strncpy(str_left_f, str_left, sizeof(str_left_f) - 1);
574 str_left_f[sizeof(str_left_f) - 1] = '\0';
575 len = split_line(str_left_f, STR_TOP_LEFT_MAX_LEN / 2, &eol, &str_left_len, 1);
576 str_left_f[len] = '\0';
577
578 strncpy(str_middle_f, str_middle, sizeof(str_middle_f) - 1);
579 str_middle_f[sizeof(str_middle_f) - 1] = '\0';
580 len = split_line(str_middle, STR_TOP_MIDDLE_MAX_LEN / 2, &eol, &str_middle_len, 1);
581 str_middle_f[len] = '\0';
582
583 strncpy(str_right_f, str_right, sizeof(str_right_f) - 1);
584 str_right_f[sizeof(str_right_f) - 1] = '\0';
585 len = split_line(str_right, STR_TOP_RIGHT_MAX_LEN / 2, &eol, &str_right_len, 1);
586 str_right_f[len] = '\0';
587
588 moveto(1, 0);
589 clrtoeol();
590 prints("\033[1;44;33m%s\033[37m%*s%s\033[33m%*s%s\033[m",
591 str_left_f, 44 - str_left_len - str_middle_len, "",
592 str_middle_f, 36 - str_right_len, "", str_right_f);
593
594 return 0;
595 }
596
597 int show_bottom(const char *msg)
598 {
599 char str_time[LINE_BUFFER_LEN];
600 time_t time_online;
601 struct tm *tm_online;
602 char msg_f[LINE_BUFFER_LEN];
603 int eol;
604 int msg_len;
605 int len;
606 int len_username;
607 char str_tm_online[LINE_BUFFER_LEN];
608
609 get_time_str(str_time, sizeof(str_time));
610
611 msg_f[0] = '\0';
612 msg_len = 0;
613 if (msg != NULL)
614 {
615 strncpy(msg_f, msg, sizeof(msg_f) - 1);
616 msg_f[sizeof(msg_f) - 1] = '\0';
617 len = split_line(msg_f, 23, &eol, &msg_len, 1);
618 msg_f[len] = '\0';
619 }
620
621 len_username = (int)strnlen(BBS_username, sizeof(BBS_username));
622
623 time_online = time(NULL) - BBS_login_tm;
624 tm_online = gmtime(&time_online);
625 if (tm_online->tm_mday > 1)
626 {
627 snprintf(str_tm_online, sizeof(str_tm_online),
628 "\033[36m%2d\033[33m天\033[36m%2d\033[33m时",
629 tm_online->tm_mday - 1, tm_online->tm_hour);
630 }
631 else
632 {
633 snprintf(str_tm_online, sizeof(str_tm_online),
634 "\033[36m%2d\033[33m时\033[36m%2d\033[33m分",
635 tm_online->tm_hour, tm_online->tm_min);
636 }
637
638 moveto(SCREEN_ROWS, 0);
639 clrtoeol();
640 prints("\033[1;44;33m时间[\033[36m%s\033[33m]%s%*s \033[33m帐号[\033[36m%s\033[33m][%s\033[33m]\033[m",
641 str_time, msg_f, 38 - msg_len - len_username, "", BBS_username, str_tm_online);
642
643 return 0;
644 }
645
646 int show_active_board()
647 {
648 static int line_current = 0;
649 static const void *p_shm = NULL;
650 static size_t data_len;
651 static long line_total;
652 static const void *p_data;
653 static const long *p_line_offsets;
654
655 static time_t t_last_show = 0;
656 static int line_last = 0;
657
658 char buffer[LINE_BUFFER_LEN];
659 long int len;
660
661 if (p_shm == NULL)
662 {
663 if ((p_shm = get_file_shm_readonly(DATA_ACTIVE_BOARD, &data_len, &line_total, &p_data, &p_line_offsets)) == NULL)
664 {
665 log_error("get_file_shm(%s) error\n", DATA_ACTIVE_BOARD);
666 return KEY_NULL;
667 }
668 }
669
670 if (time(NULL) - t_last_show >= 10)
671 {
672 line_last = line_current;
673 t_last_show = time(NULL);
674 }
675 else
676 {
677 line_current = line_last;
678 }
679
680 clrline(2, 2 + ACTIVE_BOARD_HEIGHT);
681
682 for (int i = 0; i < ACTIVE_BOARD_HEIGHT; i++)
683 {
684 len = p_line_offsets[line_current + 1] - p_line_offsets[line_current];
685 if (len >= LINE_BUFFER_LEN)
686 {
687 log_error("buffer overflow: len=%ld(%ld - %ld) line=%ld \n",
688 len, p_line_offsets[line_current + 1], p_line_offsets[line_current], line_current);
689 len = LINE_BUFFER_LEN - 1;
690 }
691
692 memcpy(buffer, (const char *)p_data + p_line_offsets[line_current], (size_t)len);
693 buffer[len] = '\0';
694
695 moveto(3 + i, 0);
696 prints("%s", buffer);
697
698 line_current++;
699 if (line_current >= line_total)
700 {
701 line_current = 0;
702 break;
703 }
704 }
705
706 return 0;
707 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1