1 /* Copyright 2013 Akira Ohta (akohta001@gmail.com)
2 This file is part of ntch.
4 The ntch is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 The ntch is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with ntch. If not, see <http://www.gnu.org/licenses/>.
18 #include <sys/types.h>
19 #include <sys/socket.h>
26 #include <netinet/in.h>
31 #include "nt_string.h"
32 #include "utils/nt_std_t.h"
33 #include "net/nt_http.h"
34 #include "net/nt_socket.h"
35 #include "net/nt_cookie.h"
36 #include "utils/file.h"
37 #include "usr/usr_db_t.h"
38 #include "utils/nt_mutex.h"
39 #include "utils/nt_pthread.h"
40 #include "utils/nt_timer.h"
41 #include "utils/base64.h"
42 #include "utils/crypt.h"
43 #include "usr/favorite_t.h"
44 #include "_2ch/_2ch.h"
45 #include "_2ch/maru_2ch.h"
46 #include "_2ch/search_2ch.h"
48 #include "ui/disp_win.h"
49 #include "ui/disp_string.h"
51 #include "cloud/nt_cloud.h"
55 static int draw_title(WINDOW *wp, const wchar_t *title, attr_t attr);
57 static BOOL DoLoop(WINDOW *scrp, nt_usr_db_handle db_handle,
58 nt_cloud_handle h_cloud,
59 nt_favorite_handle h_favorite,
60 nt_favorite_grp_handle h_favorite_grp);
62 static BOOL DoLoop(WINDOW *scrp, nt_usr_db_handle db_handle,
63 nt_favorite_handle h_favorite,
64 nt_favorite_grp_handle h_favorite_grp);
66 static void print_error(WINDOW *wp, const wchar_t *msg);
68 static void _2ch_selected_item_free(void *ptr)
70 nt_2ch_selected_item_release_ref(
71 (nt_2ch_selected_item_handle)ptr);
75 int main(int argc, char *argv[])
79 nt_usr_db_handle usr_db_handle;
80 nt_favorite_handle h_favorite;
81 nt_favorite_grp_handle h_favorite_grp;
82 nt_link_tp text_linkp, text2_linkp;
85 nt_cloud_handle h_cloud;
90 setlocale(LC_ALL, "ja_JP.UTF-8");
92 if(0 != set_option(argc, argv)){
99 usr_db_handle = nt_usr_db_init_lib(USR_LOG_DB_PATH);
101 fputs("Couldn't initialize usr database.\n", stderr);
108 fputs("Couldn't initialize Curses libraly.\n", stderr);
117 if(!nt_pthread_lib_init(
118 NT_PTHREAD_POOL_SIZE,
119 NT_PTHREAD_POOL_QUEUE_SIZE,
122 fputs(err_msg, stderr);
127 if(nt_crypt_lib_init(RFC2898_SALT, RFC2898_ITERATION,
128 AES256_PASS, &err_msg)){
129 if(NCE_AUTH_URL && NCE_ID && NCE_PASS){
130 h_cloud = nt_cloud_init(NCE_AUTH_URL, NCE_ID, NCE_PASS);
134 fputs(err_msg, stderr);
140 if(!nt_2ch_model_init()){
141 fputs("Failed to read board menu data.\n", stderr);
145 if(!nt_init_board_menu()){
146 fputs("Failed to initialize board menu data.\n", stderr);
151 h_favorite = nt_favorite_alloc(L"favorite");
155 h_favorite_grp = nt_favorite_grp_alloc(h_favorite,
157 NT_FAVORITE_GRP_FLAG_FOLDER_OPEN);
159 nt_favorite_release_ref(h_favorite);
166 text_linkp = nt_cloud_download_file(h_cloud, "fb.txt");
170 text_linkp = nt_read_text_file(USR_FAVORITE_BOARD_FILE_PATH);
172 nt_favorite_load_boards(h_favorite, text_linkp);
173 nt_all_link_free(text_linkp, free);
178 text_linkp = nt_cloud_download_file(h_cloud, "ft.txt");
182 text_linkp = nt_read_text_file(USR_FAVORITE_THREAD_FILE_PATH);
184 nt_favorite_load_threads(h_favorite, h_favorite_grp, text_linkp);
185 nt_all_link_free(text_linkp, free);
192 if(DoLoop(scrp, usr_db_handle, h_cloud, h_favorite, h_favorite_grp))
194 if(DoLoop(scrp, usr_db_handle, h_favorite, h_favorite_grp))
201 text_linkp = nt_favorite_retrieve_boards(h_favorite);
203 nt_write_text_file(USR_FAVORITE_BOARD_FILE_PATH, text_linkp);
204 nt_all_link_free(text_linkp, free);
206 unlink(USR_FAVORITE_BOARD_FILE_PATH);
208 if(nt_favorite_retrieve_threads(h_favorite,
209 &text_linkp, &text2_linkp)){
211 nt_all_link_free(text_linkp, free);
213 nt_write_text_file(USR_FAVORITE_THREAD_FILE_PATH, text2_linkp);
214 nt_all_link_free(text2_linkp, free);
216 unlink(USR_FAVORITE_THREAD_FILE_PATH);
220 nt_favorite_grp_release_ref(h_favorite_grp);
221 nt_favorite_release_ref(h_favorite);
227 nt_2ch_model_release_ref(app_2ch_model);
231 nt_usr_db_finish_lib(usr_db_handle);
233 nt_pthread_lib_finish();
235 nt_mutex_lib_finish();
237 nt_timer_lib_finish();
240 nt_crypt_lib_finish();
242 nt_cloud_release_ref(h_cloud);
248 #define TIMER_ID_AUTO_UPDATE_NONE 0
249 #define TIMER_ID_AUTO_UPDATE 1
250 #define TIMER_ID_AUTO_SCROLL 2
251 static int auto_update_timer_func(int id)
254 case TIMER_ID_AUTO_UPDATE:
255 return TIMER_ID_AUTO_UPDATE;
256 case TIMER_ID_AUTO_SCROLL:
257 return TIMER_ID_AUTO_SCROLL;
259 return TIMER_ID_AUTO_UPDATE_NONE;
263 static BOOL DoLoop(WINDOW *scrp, nt_usr_db_handle db_handle,
264 nt_cloud_handle h_cloud,
265 nt_favorite_handle h_favorite,
266 nt_favorite_grp_handle h_favorite_grp)
268 static BOOL DoLoop(WINDOW *scrp, nt_usr_db_handle db_handle,
269 nt_favorite_handle h_favorite,
270 nt_favorite_grp_handle h_favorite_grp)
274 int disp_state, nresult;
275 nt_window_tp bwinp = NULL;
276 nt_window_tp twinp = NULL;
277 nt_window_tp rwinp = NULL;
278 nt_window_tp search_winp = NULL;
279 nt_window_tp favorite_winp = NULL;
281 wchar_t title_buf[128];
283 const wchar_t *title, *board_name;
284 const wchar_t *status_msg;
286 nt_write_data_handle h_write_data;
287 nt_maru_2ch_tp marup;
288 nt_cookie_tp cookiep;
290 nt_searched_thread_handle h_searched_thread;
291 nt_2ch_selected_item_handle h_sel_items;
292 nt_2ch_selected_item_handle h_sel_items_tmp;
293 nt_favorite_board_handle h_favorite_board;
294 nt_favorite_thread_handle h_favorite_thread;
296 nt_pthread_result_t async_data;
297 nt_link_tp linkp, linkp2;
299 nt_timer_handle h_timer_auto_update;
300 nt_timer_handle h_timer_auto_scroll;
301 nt_timer_handle h_timer;
303 state = DISP_STATE_BOARDMENU;
304 if(INIT_DISP_STATE == NT_INTI_DISP_FAVORITE){
305 disp_state = DISP_STATE_FAVORITE;
307 disp_state = DISP_STATE_BOARDMENU;
309 cookiep = nt_load_cookies(USR_COOKIE_PATH);
311 if(MARU_ID && MARU_PW)
312 marup = nt_maru_2ch_alloc(MARU_ID, MARU_PW);
313 h_timer_auto_scroll = nt_timer_alloc(
314 TIMER_ID_AUTO_SCROLL, -1, auto_update_timer_func);
315 h_timer_auto_update = nt_timer_alloc(
316 TIMER_ID_AUTO_UPDATE, NT_AUTO_UPDATE_INTERVAL, auto_update_timer_func);
321 h_searched_thread = NULL;
322 h_sel_items_tmp = NULL;
323 h_sel_items = nt_2ch_selected_item_alloc();
328 bwinp = nt_disp_win_alloc(scrp, LINES-1, COLS, 1, 0, buf);
331 twinp = nt_disp_win_alloc(scrp, LINES-1, COLS, 1, 0, buf);
334 rwinp = nt_disp_win_alloc(scrp, LINES-1, COLS, 1, 0, buf);
339 keypad(stdscr, true);
348 case DISP_STATE_BOARDMENU:
351 draw_title(scrp, L"板一覧", WA_REVERSE);
352 if(!nt_disp_win_move(scrp, bwinp, LINES-1, COLS, 1, 0))
354 state = disp_board_menu(bwinp, app_2ch_model, h_sel_items);
356 if(DISP_CMD(state) == DISP_CMD_ADD_FAVORITE){
357 board_name = nt_2ch_selected_item_get_board_name(h_sel_items);
358 h_favorite_board = nt_favorite_board_alloc(
359 h_favorite, board_name);
360 if(h_favorite_board){
362 linkp = nt_link_add_data(NULL, (void*)board_name);
365 nt_cloud_insert_lines_into_file(h_cloud, "fb.txt", linkp);
369 nt_favorite_board_release_ref(h_favorite_board);
370 //favorite_dump(h_favorite);
373 state = DISP_STATE_BOARDMENU;
376 if(DISP_STATE_ERROR == state){
378 }else if(DISP_STATE_THREADTITLE == state){
379 if(nt_read_board(h_sel_items)){
381 state = DISP_STATE_BOARDMENU;
383 free_threadlist_ctx(twinp->data);
389 print_error(scrp, NT_ERR_MSG_COUDLNOT_READ_BOARD);
391 }else if(state == DISP_STATE_SEARCH_THREAD){
392 state = DISP_STATE_BOARDMENU;
393 disp_state = DISP_STATE_SEARCH_THREAD;
396 }else if(DISP_STATE_FAVORITE == state){
397 state = DISP_STATE_BOARDMENU;
398 disp_state = DISP_STATE_FAVORITE;
403 case DISP_STATE_THREADTITLE:
406 twinp->status_msg = NULL;
407 title = nt_2ch_selected_item_get_board_name(h_sel_items);
408 draw_title(scrp, title, WA_REVERSE);
409 if(!nt_disp_win_move(scrp, twinp, LINES-1, COLS, 1, 0))
411 state = disp_threadlist(twinp, state, h_sel_items, db_handle);
413 if(DISP_CMD(state) == DISP_CMD_ADD_FAVORITE){
414 h_favorite_thread = nt_favorite_thread_alloc(
416 nt_2ch_selected_item_get_thread_dat_name(h_sel_items),
417 nt_2ch_selected_item_get_board_name(h_sel_items),
418 nt_2ch_selected_item_get_thread_title(h_sel_items));
419 if(h_favorite_thread){
422 if(nt_favorite_retrieve_thread(h_favorite_thread, &linkp)){
424 nt_cloud_insert_lines_into_file(h_cloud, "ft.txt", linkp);
425 nt_all_link_free(linkp, free);
428 nt_favorite_thread_release_ref(h_favorite_thread);
429 //favorite_dump(h_favorite);
430 status_msg = NT_INFO_ADD_FAVORITE_SUCCEEDED;
432 status_msg = NT_ERR_MSG_ADD_FAVORITE_FAILED;
435 state = DISP_STATE_THREADTITLE;
437 }else if(DISP_STATE_ERROR == state){
439 }else if(DISP_STATE_BOARDMENU == state){
443 }else if(DISP_STATE_REFRESH == state){
444 if(nt_read_board(h_sel_items)){
446 free_threadlist_ctx(twinp->data);
450 status_msg = NT_INFO_REFRESH_BOARD_SUCCESS;
451 state = DISP_STATE_THREADTITLE;
455 status_msg = NT_ERR_MSG_COUDLNOT_READ_BOARD;
457 state = DISP_STATE_THREADTITLE;
458 }else if(DISP_STATE_RESLIST == state){
460 if(!nt_read_thread(h_sel_items)){
462 state = DISP_STATE_THREADTITLE;
470 init_threadlist_ctx(twinp->data);
473 free_reslist_ctx(rwinp->data);
476 state = DISP_STATE_THREADTITLE;
479 }else if(DISP_STATE_SEARCH_THREAD == state){
480 state = DISP_STATE_THREADTITLE;
481 disp_state = DISP_STATE_SEARCH_THREAD;
484 }else if(DISP_STATE_FAVORITE == state){
485 state = DISP_STATE_THREADTITLE;
486 disp_state = DISP_STATE_FAVORITE;
490 status_msg = twinp->status_msg;
493 case DISP_STATE_RESLIST:
495 title = nt_2ch_selected_item_get_thread_title(h_sel_items);
497 num = draw_title(scrp, L"Title not found.", WA_REVERSE);
498 }else if(-1 == swprintf(title_buf, sizeof(title_buf)/sizeof(wchar_t),
500 nt_2ch_selected_item_get_board_name(h_sel_items),
502 num = draw_title(scrp, title, WA_REVERSE);
504 num = draw_title(scrp, title_buf, WA_REVERSE);
506 if(!nt_disp_win_move(scrp, rwinp,
507 LINES - num, COLS, num, 0))
510 state = disp_reslist(rwinp, state, h_sel_items, db_handle);
511 auto_scrolling = FALSE;
513 if(DISP_CMD(state) == DISP_CMD_AUTO_SCROLL){
514 if(0 >= nt_timer_get_interval(h_timer_auto_scroll)){
515 nt_timer_set_interval(
516 h_timer_auto_scroll, NT_AUTO_SCROLL_INTERVAL);
518 auto_scrolling = TRUE;
523 nt_timer_set_interval(h_timer_auto_scroll, -1);
525 if(DISP_STATE_ERROR == state){
527 }else if(state == DISP_STATE_REFRESH){
528 state = DISP_STATE_RESLIST;
530 if(!nt_read_thread(h_sel_items)){
531 status_msg = NT_ERR_MSG_REFRESH_THREAD_FAILED;
536 free_reslist_ctx(rwinp->data);
539 status_msg = NT_INFO_REFRESH_THREAD_SUCCESS;
542 }else if(state == DISP_STATE_SEARCH_THREAD){
543 state = DISP_STATE_RESLIST;
544 disp_state = DISP_STATE_SEARCH_THREAD;
547 }else if(DISP_STATE_FAVORITE == state){
548 state = DISP_STATE_RESLIST;
549 disp_state = DISP_STATE_FAVORITE;
552 }else if(state != DISP_STATE_RESLIST){
558 case DISP_STATE_EDITOR:
559 disp_state = DISP_STATE_RESLIST;
560 state = DISP_STATE_RESLIST;
563 nt_write_data_release_ref(h_write_data);
564 h_write_data = nt_write_data_alloc();
567 if(disp_editor(h_write_data)){
568 if(marup && !marup->sid)
569 get_session_id(marup);
570 if(nt_write_msg(h_sel_items,
571 h_write_data, cookiep, marup)){
572 disp_state = DISP_STATE_HTML_RESULT;
576 keypad(stdscr, true);
577 status_msg = nt_write_data_get_status_msg(h_write_data);
580 case DISP_STATE_HTML_RESULT:
581 nresult = disp_html_result(h_write_data);
583 if(!nt_read_thread(h_sel_items))
586 free_reslist_ctx(rwinp->data);
589 }else if(1 == nresult){
590 if(nt_write_msg(h_sel_items,
591 h_write_data, cookiep, marup)){
592 if(0 == disp_html_result(h_write_data)){
593 if(!nt_read_thread(h_sel_items))
596 free_reslist_ctx(rwinp->data);
602 keypad(stdscr, true);
603 disp_state = DISP_STATE_RESLIST;
604 state = DISP_STATE_RESLIST;
606 status_msg = nt_write_data_get_status_msg(h_write_data);
609 case DISP_STATE_SEARCH_THREAD:
610 draw_title(scrp, L"全板検索", WA_REVERSE);
612 search_winp = nt_disp_win_alloc(
613 scrp, LINES-1, COLS, 1, 0, buf);
616 search_winp->key = ch;
618 if(nt_get_search_text(buf, &search)){
620 linkp = nt_search_all_board(
621 app_2ch_model, search, &status_msg);
626 h_searched_thread = NULL;
627 disp_state = disp_thread_search(search_winp,
628 state, linkp, &h_searched_thread);
630 if(h_searched_thread){
631 if(nt_set_sel_item(app_2ch_model,
632 &h_sel_items_tmp, h_searched_thread, &status_msg)){
633 disp_state = DISP_STATE_RESLIST;
634 nt_2ch_selected_item_release_ref(h_sel_items);
635 h_sel_items = h_sel_items_tmp;
637 free_reslist_ctx(rwinp->data);
641 free_threadlist_ctx(twinp->data);
644 nt_searched_thread_release_ref(h_searched_thread);
645 h_searched_thread = NULL;
648 nt_searched_thread_release_ref(h_searched_thread);
649 h_searched_thread = NULL;
651 if(disp_state != DISP_STATE_SEARCH_THREAD){
655 case DISP_STATE_FAVORITE:
656 draw_title(scrp, L"お気に入り", WA_REVERSE);
658 favorite_winp = nt_disp_win_alloc(
659 scrp, LINES-1, COLS, 1, 0, buf);
662 favorite_winp->key = ch;
663 h_searched_thread = NULL;
664 if(!favorite_winp->data){
665 linkp = nt_favorite_get_update_board_list(app_2ch_model, h_favorite);
667 nt_read_board_list(linkp);
668 nt_all_link_free(linkp, _2ch_selected_item_free);
671 state = disp_favorite(favorite_winp, state, app_2ch_model,
672 h_favorite, db_handle,
673 &handle, &h_searched_thread);
676 if(DISP_CMD(state) == DISP_CMD_DEL_FAVORITE_BOARD){
677 if(nt_favorite_board_remove(handle)){
678 h_favorite_board = (nt_favorite_board_handle)handle;
681 linkp = nt_link_add_data(NULL,
682 (void*)nt_favorite_board_get_name(h_favorite_board));
684 nt_cloud_delete_lines_from_file(h_cloud, "fb.txt", linkp);
689 nt_favorite_board_release_ref(h_favorite_board);
690 status_msg = NT_INFO_DEL_FAVORITE_SUCCEEDED;
692 status_msg = NT_ERR_MSG_DEL_FAVORITE_FAILED;
694 }else if(DISP_CMD(state) == DISP_CMD_DEL_FAVORITE_THREAD){
695 if(nt_favorite_thread_remove(handle)){
696 h_favorite_thread = (nt_favorite_thread_handle)handle;
700 if(nt_favorite_retrieve_thread(h_favorite_thread, &linkp)){
701 nt_cloud_delete_lines_from_file(h_cloud, "ft.txt", linkp);
702 nt_all_link_free(linkp, free);
706 nt_favorite_thread_release_ref(h_favorite_thread);
707 status_msg = NT_INFO_DEL_FAVORITE_SUCCEEDED;
709 status_msg = NT_ERR_MSG_DEL_FAVORITE_FAILED;
711 }else if(DISP_CMD(state) == DISP_CMD_SEL_FAVORITE_BOARD ||
712 DISP_CMD(state) == DISP_CMD_SEL_FAVORITE_THREAD){
713 assert(h_searched_thread);
714 if(nt_set_sel_item(app_2ch_model,
715 &h_sel_items_tmp, h_searched_thread, &status_msg)){
716 disp_state = (DISP_CMD(state) == DISP_CMD_SEL_FAVORITE_BOARD)
717 ? DISP_STATE_THREADTITLE : DISP_STATE_RESLIST;
718 state = DISP_STATE_FAVORITE;
719 nt_2ch_selected_item_release_ref(h_sel_items);
720 h_sel_items = h_sel_items_tmp;
722 free_reslist_ctx(rwinp->data);
726 free_threadlist_ctx(twinp->data);
730 nt_searched_thread_release_ref(h_searched_thread);
731 h_searched_thread = NULL;
734 if(DISP_CMD(state) == DISP_CMD_UPLOAD_ALL ||
735 DISP_CMD(state) == DISP_CMD_UPLOAD_BOARDS){
736 status_msg = NT_ERR_MSG_UPLOAD_FAVORITE_FAILED;
737 linkp = nt_favorite_retrieve_boards(h_favorite);
739 if(nt_cloud_upload_file(h_cloud, "fb.txt", linkp)){
740 status_msg = NT_INFO_UPLOAD_FAVORITE_SUCCEEDED;
742 nt_all_link_free(linkp, free);
745 if(DISP_CMD(state) == DISP_CMD_UPLOAD_ALL ||
746 DISP_CMD(state) == DISP_CMD_UPLOAD_THREADS){
747 if(nt_favorite_retrieve_threads(h_favorite,
750 nt_all_link_free(linkp, free);
752 if(nt_cloud_upload_file(h_cloud, "ft.txt", linkp2)){
753 status_msg = NT_INFO_UPLOAD_FAVORITE_SUCCEEDED;
755 nt_all_link_free(linkp2, free);
759 disp_state = state = DISP_STATE_FAVORITE;
762 disp_state = state = DISP_STATE_FAVORITE;
765 }else if(state == DISP_STATE_REFRESH){
766 state = DISP_STATE_FAVORITE;
768 if(favorite_winp->data){
769 free_favorite_ctx(favorite_winp->data);
770 favorite_winp->data = NULL;
772 status_msg = NT_INFO_REFRESH_FAVORITE_SUCCESS;
775 }else if(state != DISP_STATE_FAVORITE){
780 disp_state = DISP_STATE_FAVORITE;
788 print_error(scrp, status_msg);
792 move(LINES-1,COLS-1);
796 timeout(NT_MAINLOOP_POLLING_INTERVAL);
800 async_data = nt_pthread_get_result_from_que();
801 if(async_data.code == NT_PTHREAD_RESULT_UPDATE_BOARD){
802 ch = NT_KEY_CMD_BOARD_UPDATE;
803 //fprintf(stderr, "Async result recieved.!\n");
805 h_timer = nt_timer_ring_a_bell();
807 switch(nt_timer_get_id(h_timer)){
808 case TIMER_ID_AUTO_SCROLL:
809 ch = NT_KEY_CMD_AUTO_SCROLL;
811 case TIMER_ID_AUTO_UPDATE:
812 linkp = nt_favorite_get_update_board_list(
813 app_2ch_model, h_favorite);
815 nt_read_board_list(linkp);
816 nt_all_link_free(linkp, _2ch_selected_item_free);
817 //fprintf(stderr, "Update request calling.!\n");
821 nt_timer_release_ref(h_timer);
825 }while(ch == '\n' || ch == '\r' || ch == ERR);
838 case NT_KEY_PAGEDOWN:
852 ch = NT_KEY_COMMAND1;
853 nt_add_wch(scrp,ch,0);
856 case NT_KEY_COMMAND1:
857 case NT_KEY_COMMAND2:
858 case NT_KEY_COMMAND3:
863 nt_add_wch(scrp,wch,0);
877 if(h_timer_auto_update)
878 nt_timer_release_ref(h_timer_auto_update);
879 if(h_timer_auto_scroll)
880 nt_timer_release_ref(h_timer_auto_scroll);
882 nt_unload_cookie(cookiep);
884 nt_maru_2ch_free(marup);
886 nt_write_data_release_ref(h_write_data);
887 if(h_searched_thread)
888 nt_searched_thread_release_ref(h_searched_thread);
889 nt_2ch_selected_item_release_ref(h_sel_items);
890 free_board_menu_ctx(bwinp->data);
891 free_threadlist_ctx(twinp->data);
892 free_reslist_ctx(rwinp->data);
894 if(search_winp->data)
895 free_search_thread_ctx(search_winp->data);
896 nt_disp_win_free(search_winp);
899 if(favorite_winp->data)
900 free_favorite_ctx(favorite_winp->data);
901 nt_disp_win_free(favorite_winp);
903 nt_disp_win_free(bwinp);
904 nt_disp_win_free(twinp);
905 nt_disp_win_free(rwinp);
912 static int draw_title(WINDOW *wp, const wchar_t *title, attr_t attr)
916 num = nt_get_wc_count_within_colmns(title, COLS-1);
920 nt_add_wnch(wp, L' ', attr, COLS);
922 nt_add_wnstr(wp, title, WA_REVERSE, COLS - 1);
928 static void print_error(WINDOW *wp, const wchar_t *msg)
931 nt_add_wstr(wp, msg, 0);