Advertisement
Guest User

cursedchat.c

a guest
Mar 27th, 2015
218
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.45 KB | None | 0 0
  1. /*
  2. cursedchat, a simple curses interface for tc_client
  3. Copyright (C) 2015 alicia@ion.nu
  4.  
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU Affero General Public License as published by
  7. the Free Software Foundation, version 3 of the License.
  8.  
  9. This program 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 Affero General Public License for more details.
  13.  
  14. You should have received a copy of the GNU Affero General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include <unistd.h>
  18. #include <stdlib.h>
  19. #include <poll.h>
  20. #include <signal.h>
  21. #include <sys/ioctl.h>
  22. #include <termios.h>
  23. #include <locale.h>
  24. #include <curses.h>
  25. #include <readline/readline.h>
  26. #include <readline/history.h>
  27. #include "../compat.h"
  28. #include "../list.h"
  29. #include "buffer.h"
  30.  
  31. #define HALFSCREEN (LINES>4?(LINES-3)/2:1)
  32.  
  33. char* nickname=0;
  34. WINDOW* topic;
  35. char* channeltopic;
  36. WINDOW* input;
  37. int to_app;
  38. struct list userlist={0,0};
  39.  
  40. // Translate ANSI escape codes to curses commands and write the text to a window
  41. void waddansi(WINDOW* w, char* str)
  42. {
  43. while(str[0])
  44. {
  45. char* esc=strstr(str, "\x1b[");
  46. if(esc==str)
  47. {
  48. str=&str[2];
  49. while(str[0]!='m')
  50. {
  51. if(str[0]=='3'&&str[1]!='m') // Color
  52. {
  53. unsigned int c=strtoul(&str[1], &str, 10);
  54. wattron(w, COLOR_PAIR(c+1));
  55. }
  56. else if(str[0]=='1') // Bold
  57. {
  58. wattron(w, A_BOLD);
  59. str=&str[1];
  60. }
  61. else if(str[0]=='0') // Reset
  62. {
  63. wattroff(w, COLOR_PAIR(1));
  64. wattroff(w, A_BOLD);
  65. str=&str[1];
  66. }
  67. else{str=&str[1];}
  68. }
  69. str=&str[1];
  70. continue;
  71. }
  72. if(esc)
  73. {
  74. waddnstr(w, str, esc-str);
  75. str=esc;
  76. }else{
  77. waddstr(w, str);
  78. return;
  79. }
  80. }
  81. }
  82.  
  83. void drawchat(void)
  84. {
  85. WINDOW* w=buffers[currentbuf].pad;
  86. int scroll=buffers[currentbuf].scroll;
  87. prefresh(w, (scroll>-1?scroll:getcury(w)-LINES+4), 0, 1, 0, LINES-3, COLS);
  88. }
  89.  
  90. void drawtopic(void)
  91. {
  92. werase(topic);
  93. unsigned int i;
  94. for(i=1; i<buffercount && buffers[i].seen; ++i);
  95. if(i<buffercount)
  96. {
  97. waddstr(topic, "Unread PMs from: ");
  98. char first=1;
  99. for(i=1; i<buffercount; ++i)
  100. {
  101. if(!buffers[i].seen)
  102. {
  103. if(first){first=0;}else{waddstr(topic, ", ");}
  104. waddstr(topic, buffers[i].name);
  105. }
  106. }
  107. }
  108. else if(currentbuf)
  109. {
  110. waddstr(topic, "To return to public chat type: /pm");
  111. }else{
  112. waddstr(topic, channeltopic);
  113. }
  114. wrefresh(topic);
  115. }
  116.  
  117. void gotline(char* line)
  118. {
  119. if(!line){close(to_app); return;} // TODO: handle EOF on stdin better?
  120. add_history(line);
  121. if(!strcmp(line, "/pm"))
  122. {
  123. currentbuf=0;
  124. drawchat();
  125. drawtopic();
  126. return;
  127. }
  128. else if(!strncmp(line, "/pm ", 4))
  129. {
  130. currentbuf=findbuffer(&line[4]);
  131. if(!currentbuf){currentbuf=createbuffer(&line[4]);}
  132. buffers[currentbuf].seen=1;
  133. drawchat();
  134. drawtopic();
  135. return;
  136. }
  137. else if(!strncmp(line, "/buffer ", 8))
  138. {
  139. unsigned int num=atoi(&line[8]);
  140. if(num<0 || num>=buffercount)
  141. {
  142. wprintw(buffers[currentbuf].pad, "\nInvalid buffer number: %u", num);
  143. }else{
  144. currentbuf=num;
  145. buffers[currentbuf].seen=1;
  146. drawtopic();
  147. }
  148. drawchat();
  149. return;
  150. }
  151. else if(!strcmp(line, "/bufferlist"))
  152. {
  153. unsigned int i;
  154. for(i=0; i<buffercount; ++i)
  155. {
  156. wprintw(buffers[currentbuf].pad, "\n% 3i: %s", i, i?buffers[i].name:"");
  157. }
  158. drawchat();
  159. return;
  160. }
  161. else if(!strncmp(line, "/msg ", 5))
  162. {
  163. char* name=&line[5];
  164. char* msg=strchr(name, ' ');
  165. if(!msg){return;}
  166. msg[0]=0;
  167. currentbuf=findbuffer(name);
  168. if(!currentbuf){currentbuf=createbuffer(name);}
  169. buffers[currentbuf].seen=1;
  170. drawtopic();
  171. memmove(line, &msg[1], strlen(&msg[1])+1);
  172. }
  173. else if(!strcmp(line, "/help"))
  174. {
  175. waddstr(buffers[0].pad, "\nFor cursedchat:\n"
  176. "/pm <name> = switch to the PM buffer for <name>\n"
  177. "/pm = return to the channel/public chat's buffer\n"
  178. "/buffer <num> = switch to buffer by number\n"
  179. "/bufferlist = list open buffers, their numbers and associated names\n"
  180. "\nFor tc_client (through cursedchat):");
  181. write(to_app, line, strlen(line));
  182. write(to_app, "\n", 1);
  183. return;
  184. }
  185.  
  186. if(currentbuf) // We're in a PM window, make the message a PM
  187. {
  188. dprintf(to_app, "/msg %s ", buffers[currentbuf].name);
  189. }
  190. write(to_app, line, strlen(line));
  191. write(to_app, "\n", 1);
  192. // TODO: grab user's nick for this
  193. wprintw(buffers[currentbuf].pad, "\n%s: %s", nickname, line);
  194. drawchat();
  195. }
  196.  
  197. unsigned int bytestochars(const char* buf, unsigned int buflen, unsigned int bytes)
  198. {
  199. unsigned int pos=0;
  200. unsigned int i;
  201. for(i=0; i<bytes; ++pos)
  202. {
  203. i+=mbtowc(0,&buf[i],buflen-i);
  204. }
  205. return pos;
  206. }
  207.  
  208. unsigned int charstobytes(const char* buf, unsigned int buflen, unsigned int chars)
  209. {
  210. unsigned int pos;
  211. unsigned int i=0;
  212. for(pos=0; pos<chars; ++pos)
  213. {
  214. i+=mbtowc(0,&buf[i],buflen-i);
  215. }
  216. return i;
  217. }
  218.  
  219. int escinput(int a, int byte)
  220. {
  221. char buf[4];
  222. read(0, buf, 2);
  223. buf[2]=0;
  224. if(!strcmp(buf, "[A")||!strcmp(buf, "OA")){rl_get_previous_history(1,27);return 0;}
  225. if(!strcmp(buf, "[B")||!strcmp(buf, "OB")){rl_get_next_history(1,27);return 0;}
  226. if(!strcmp(buf, "[C")||!strcmp(buf, "OC")){rl_forward(1,27);return 0;}
  227. if(!strcmp(buf, "[D")||!strcmp(buf, "OD")){rl_backward(1,27);return 0;}
  228. if(!strcmp(buf, "[H")||!strcmp(buf, "OH")){rl_beg_of_line(1,27);return 0;}
  229. if(!strcmp(buf, "[F")||!strcmp(buf, "OF")){rl_end_of_line(1,27);return 0;}
  230. if(!strcmp(buf, "[3")&&read(0, buf, 1)&&buf[0]=='~'){rl_delete(1,27);return 0;}
  231. if(!strcmp(buf, "[5")) // Page up
  232. {
  233. read(0, buf, 1);
  234. struct buffer* b=&buffers[currentbuf];
  235. if(b->scroll<0){b->scroll=getcury(b->pad)-LINES+4;}
  236. b->scroll-=HALFSCREEN;
  237. if(b->scroll<0){b->scroll=0;}
  238. drawchat();
  239. return 0;
  240. }
  241. if(!strcmp(buf, "[6")) // Page down
  242. {
  243. read(0, buf, 1);
  244. struct buffer* b=&buffers[currentbuf];
  245. if(b->scroll<0){return 0;} // Already at the bottom
  246. b->scroll+=HALFSCREEN;
  247. if(b->scroll>getcury(b->pad)-LINES+3){b->scroll=-1;}
  248. drawchat();
  249. return 0;
  250. }
  251. return 0;
  252. }
  253.  
  254. void drawinput(void)
  255. {
  256. werase(input);
  257. unsigned int pos=bytestochars(rl_line_buffer, rl_end, rl_point);
  258.  
  259. waddstr(input, "> ");
  260. int cursor_row=(pos+2)/COLS;
  261. int end_row=(rl_end+2)/COLS;
  262. // Figure out how much of the buffer to print to not scroll past the cursor
  263. unsigned int eol=charstobytes(rl_line_buffer, rl_end, (cursor_row+2)*COLS-3); // -2 for cursor, -1 to avoid wrapping
  264. waddnstr(input, rl_line_buffer, eol);
  265.  
  266. wmove(input, cursor_row==end_row && cursor_row>0, (pos+2)%COLS); // +2 for prompt
  267. wrefresh(input);
  268. }
  269.  
  270. void resizechat(int sig)
  271. {
  272. struct winsize size;
  273. ioctl(0, TIOCGWINSZ, &size);
  274. if(size.ws_row<3){return;} // Too small, would result in negative numbers breaking the chat window
  275. resize_term(size.ws_row, size.ws_col);
  276. clear();
  277. refresh();
  278. wresize(topic, 1, COLS);
  279. unsigned int i;
  280. for(i=0; i<buffercount; ++i)
  281. {
  282. wresize(buffers[i].pad, buffers[i].pad->_maxy+1, COLS);
  283. }
  284. wresize(input, 2, COLS);
  285. mvwin(input, LINES-2, 0);
  286. redrawwin(buffers[currentbuf].pad);
  287. redrawwin(topic);
  288. redrawwin(input);
  289. drawchat();
  290. drawtopic();
  291. drawinput();
  292. }
  293.  
  294. void dontprintmatches(char** matches, int num, int maxlen)
  295. {
  296. }
  297.  
  298. unsigned int completionmatch;
  299. char* completenicks(const char* text, int state)
  300. {
  301. // text is the word we're completing on, state is the iteration count (one iteration per matching name, until we return 0)
  302. if(!state){completionmatch=0;}
  303. while(completionmatch<userlist.itemcount)
  304. {
  305. if(!strncmp(userlist.items[completionmatch], text, strlen(text)))
  306. {
  307. char* completion=malloc(strlen(userlist.items[completionmatch])+2);
  308. strcpy(completion, userlist.items[completionmatch]);
  309. // Check if we're on the first word and only add the ":" if we are
  310. if(strlen(text)>=rl_point){strcat(completion, ":");}
  311. ++completionmatch;
  312. return completion;
  313. }
  314. ++completionmatch;
  315. }
  316. return 0;
  317. }
  318.  
  319. int main(int argc, char** argv)
  320. {
  321. if(argc<3){execv("./tc_client", argv); return 1;}
  322. setlocale(LC_ALL, "");
  323. WINDOW* w=initscr();
  324. signal(SIGWINCH, resizechat);
  325. start_color();
  326. cbreak();
  327. noecho();
  328. keypad(w, 1);
  329. use_default_colors();
  330. topic=newwin(1, COLS, 0, 0);
  331. init_pair(1, COLOR_WHITE, COLOR_BLUE);
  332.  
  333. // Define colors mapped to ANSI color codes (at least the ones tc_client uses)
  334. init_pair(2, COLOR_RED, -1);
  335. init_pair(3, COLOR_GREEN, -1);
  336. init_pair(4, COLOR_YELLOW, -1);
  337. init_pair(5, COLOR_BLUE, -1);
  338. init_pair(6, COLOR_MAGENTA, -1);
  339. init_pair(7, COLOR_CYAN, -1);
  340.  
  341. wbkgd(topic, COLOR_PAIR(1)|' ');
  342. createbuffer(0);
  343. input=newwin(2, COLS, LINES-2, 0);
  344. scrollok(input, 1);
  345. rl_initialize();
  346. rl_callback_handler_install(0, gotline);
  347. rl_bind_key('\x1b', escinput);
  348. rl_completion_display_matches_hook=dontprintmatches;
  349. rl_completion_entry_function=completenicks;
  350. wprintw(input, "> ");
  351. wrefresh(topic);
  352. wrefresh(input);
  353. int app_in[2];
  354. int app_out[2];
  355. pipe(app_in);
  356. pipe(app_out);
  357. if(!fork())
  358. {
  359. close(app_in[1]);
  360. close(app_out[0]);
  361. dup2(app_in[0],0);
  362. dup2(app_out[1],1);
  363. argv[0]="./tc_client";
  364. execv("./tc_client", argv);
  365. _exit(1);
  366. }
  367. close(app_in[0]);
  368. close(app_out[1]);
  369. to_app=app_in[1];
  370. struct pollfd p[2]={{.fd=0, .events=POLLIN, .revents=0},
  371. {.fd=app_out[0], .events=POLLIN, .revents=0}};
  372. while(1)
  373. {
  374. poll(p, 2, -1);
  375. if(p[1].revents) // Getting data from tc_client
  376. {
  377. p[1].revents=0;
  378. char buf[1024];
  379. size_t len=0;
  380. while(len<1023)
  381. {
  382. if(read(app_out[0], &buf[len], 1)!=1){len=-1; break;}
  383. if(buf[len]=='\r'||buf[len]=='\n'){break;}
  384. ++len;
  385. }
  386. if(len==-1){break;} // Bad read
  387. buf[len]=0;
  388. unsigned int buffer=0;
  389. if(!strncmp(buf, "Room topic: ", 12))
  390. {
  391. free(channeltopic);
  392. channeltopic=strdup(&buf[12]);
  393. drawtopic();
  394. }
  395. else if(!strncmp(buf, "Connection ID: ", 15))
  396. {
  397. unsigned int length=strlen(&buf[15]);
  398. // char nick[length+strlen("guest-")+1];
  399. nickname=malloc(length+strlen("guest-")+1);
  400. sprintf(nickname,"guest-%s",&(buf[15]));
  401. }
  402. else if(!strncmp(buf, "Currently online: ", 18))
  403. {
  404. // Populate the userlist
  405. char* name=&buf[16];
  406. while(name)
  407. {
  408. name=&name[2];
  409. char* next=strstr(name, ", ");
  410. if(next){next[0]=0;}
  411. list_add(&userlist, name);
  412. if(next){next[0]=',';}
  413. name=next;
  414. }
  415. }
  416. else if(buf[0]=='['&&isdigit(buf[1])&&isdigit(buf[2])&&buf[3]==':'&&isdigit(buf[4])&&isdigit(buf[5])&&buf[6]==']'&&buf[7]==' ')
  417. {
  418. char* nick=&buf[8];
  419. char* msg=strchr(nick, ' ');
  420. if(msg[-1]==':')
  421. {
  422. nick=strchr(nick, 'm')+1;
  423. char* nickend=&msg[-1];
  424. msg=&msg[1];
  425. if(!strncmp(msg, "/msg ", 5)) // message is a PM
  426. {
  427. char* pm=strchr(&msg[5], ' ');
  428. if(!pm){waddstr(buffers[0].pad, "\npm is null!"); continue;}
  429. pm=&pm[1];
  430. nickend[0]=0;
  431. buffer=findbuffer(nick);
  432. if(!buffer){buffer=createbuffer(nick);}
  433. nickend[0]=':';
  434. memmove(msg, pm, strlen(pm)+1);
  435. if(buffer!=currentbuf)
  436. {
  437. buffers[buffer].seen=0;
  438. drawtopic();
  439. }
  440. }
  441. }
  442. else if(!strncmp(msg, " changed nickname to ", 21))
  443. {
  444. msg[0]=0;
  445. // Update name in userlist
  446. if(!strcmp(nickname, nick))
  447. {
  448. free (nickname);
  449. nickname=strdup (&msg[21]);
  450. }
  451. list_switch(&userlist, nick, &msg[21]);
  452. unsigned int i;
  453. // Prevent duplicate names for buffers, and all the issues that would bring
  454. if((i=findbuffer(&msg[21])))
  455. {
  456. renamebufferunique(i);
  457. }
  458. for(i=1; i<buffercount; ++i)
  459. {
  460. if(!strcmp(buffers[i].name, nick))
  461. {
  462. free(buffers[i].name);
  463. buffers[i].name=strdup(&msg[21]);
  464. }
  465. }
  466. msg[0]=' ';
  467. }
  468. else if(!strcmp(msg, " entered the channel"))
  469. {
  470. msg[0]=0;
  471. // Add to the userlist
  472. list_add(&userlist, nick);
  473. msg[0]=' ';
  474. }
  475. else if(!strcmp(msg, " left the channel"))
  476. {
  477. msg[0]=0;
  478. // Remove from the userlist
  479. list_del(&userlist, nick);
  480. msg[0]=' ';
  481. }
  482. }
  483. waddstr(buffers[buffer].pad, "\n");
  484. waddansi(buffers[buffer].pad, buf);
  485. drawchat();
  486. wrefresh(input);
  487. continue;
  488. }
  489. if(!p[0].revents){continue;}
  490. p[0].revents=0;
  491. rl_callback_read_char();
  492. drawinput();
  493. }
  494. rl_callback_handler_remove();
  495. endwin();
  496. return 0;
  497. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement