├── Makefile ├── README.markdown ├── examples ├── nchantview.c ├── stb_image.h ├── test-include.c └── test.c ├── nchanterm.c ├── nchanterm.h └── nchanterm.pc /Makefile: -------------------------------------------------------------------------------- 1 | CFILES = $(wildcard *.c) 2 | OBJS = $(CFILES:.c=.o) 3 | PROJECT_NAME = nchanterm 4 | LIBNAME = lib$(PROJECT_NAME) 5 | PREFIX = /usr/local 6 | LIBEXT = so 7 | TARGETS = $(LIBNAME).a $(LIBNAME).$(LIBEXT) 8 | 9 | CFLAGS += -I.. -I. 10 | CFLAGS += -O2 -g 11 | CFLAGS += -Wall 12 | CFLAGS += -fPIC 13 | 14 | all: $(TARGETS) 15 | 16 | $(LIBNAME).a: $(OBJS) 17 | @echo " AR" $@;$(AR) rcs $@ $(OBJS) 18 | $(LIBNAME).$(LIBEXT): $(OBJS) 19 | @echo " LD" $@;$(CC) -shared $(OBJS) -o $@ $(FLAGS) $(LIBS) 20 | %.o: %.c *.h 21 | @echo " CC" $<;$(CC) -c $(CFLAGS) $< -o $@ 22 | 23 | EXAMPLES_CFILES = $(wildcard examples/*.c) 24 | EXAMPLES_BINS = $(EXAMPLES_CFILES:.c=) 25 | 26 | examples/%: examples/%.c $(LIBNAME).a 27 | @echo "CCLD" $@; $(CC) -I .. $(CFLAGS) $(LIBS) $< $(LIBNAME).a -lm -o $@ 28 | 29 | all: $(EXAMPLES_BINS) 30 | 31 | clean: 32 | rm -f $(OBJS) $(TARGETS) $(BIN) $(EXAMPLES_BINS) 33 | 34 | install: $(LIBNAME).$(LIBEXT) 35 | sudo install -t $(PREFIX)/lib $(LIBNAME).$(LIBEXT) 36 | sudo install -t $(PREFIX)/lib/pkgconfig $(PROJECT_NAME).pc 37 | sudo install -d $(PREFIX)/include/$(PROJECT_NAME) 38 | sudo install -t $(PREFIX)/include/$(PROJECT_NAME) nchanterm.c nchanterm.h 39 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | nchanterm 2 | ========= 3 | 4 | UTF-8 ANSI/vt102 text mode interface. 5 | 6 | Features: 7 | --------- 8 | - UTF8 native 9 | - Color, underline, bold 10 | - Mouse and keyboard events 11 | - Internally keeps track of terminal state and only does delta updates. 12 | - Liberal license ISC which is equivalent to MIT but with a simplified language. 13 | - Single .c file that can be dropped into a project to gain text mode 14 | capabilities. 15 | 16 | The API provided is the smallest core needed to drive an application, higher 17 | level abstractions can be provided on top. 18 | 19 | Rationale 20 | --------- 21 | 22 | Terminal emulators in use today mostly share the ANSI subset of control 23 | commands, terminals this statement applies to include: 24 | 25 | Eterm, linux console, putty, rxvt, screen, st, tmux, vte (gnome-terminal), 26 | xterm and probably more. 27 | 28 | Even though they support a wide range of different commands, manipulating the 29 | character grid as well as initiating mouse-event can be achieved through a 30 | common subset. 31 | 32 | Documentation 33 | ------------- 34 | 35 | Nchanterm can be installed systemwide can be used through pkg-config based 36 | interface or to be dropped into the source tree of a project. See examples/ 37 | for some code examples using the API. For a list of supported key-codes look 38 | for the array called /keycodes/ in the source, not all key-codes work on all 39 | terminals (should perhaps remove the ones least likely to be portable). 40 | -------------------------------------------------------------------------------- /examples/nchantview.c: -------------------------------------------------------------------------------- 1 | /* a rough terminal image viewer */ 2 | 3 | #include /* sprintf */ 4 | #include 5 | #include "stb_image.h" 6 | 7 | #define NCHANTERM_HEADER_ONLY 8 | #include "nchanterm.c" 9 | 10 | #define NL do {x = 2; y++;}while(0) 11 | #define P(string) do {x += nct_print (term, x, y, string, -1);}while(0) 12 | 13 | /* XXX: should store the bitmask of each glyph, making it easier to add 14 | * and remove geometry matching blocks 15 | */ 16 | 17 | static char *utf8_gray_scale[]={" ","░","▒","▓","█","█", NULL}; 18 | 19 | static char *quarter_blocks[]= 20 | {" ","▘","▝","▀","▖","▌","▞","▛","▗","▚","▐","▜","▄","▙","▟","█", 21 | "▏","▎","▍","▋","▊","▉","▁","▂","▃","▅","▆","▇","▪","━","┃","╋", 22 | 23 | /* in reverse */ 24 | "▏","▎","▍","▋","▊","▉","▁","▂","▃","▅","▆","▇","▪","━","┃","╋", 25 | NULL}; 26 | #include 27 | 28 | /* a list of images corresponding to */ 29 | static uint64_t images[] = { 30 | 0b\ 31 | 00000000\ 32 | 00000000\ 33 | 00000000\ 34 | 00000000\ 35 | 00000000\ 36 | 00000000\ 37 | 00000000\ 38 | 00000000, 39 | 40 | 0b\ 41 | 11110000\ 42 | 11110000\ 43 | 11110000\ 44 | 11110000\ 45 | 00000000\ 46 | 00000000\ 47 | 00000000\ 48 | 00000000, 49 | 50 | 0b\ 51 | 00001111\ 52 | 00001111\ 53 | 00001111\ 54 | 00001111\ 55 | 00000000\ 56 | 00000000\ 57 | 00000000\ 58 | 00000000, 59 | 60 | 0b\ 61 | 11111111\ 62 | 11111111\ 63 | 11111111\ 64 | 11111111\ 65 | 00000000\ 66 | 00000000\ 67 | 00000000\ 68 | 00000000, 69 | 70 | 0b\ 71 | 00000000\ 72 | 00000000\ 73 | 00000000\ 74 | 00000000\ 75 | 11110000\ 76 | 11110000\ 77 | 11110000\ 78 | 11110000, 79 | 80 | 0b\ 81 | 11110000\ 82 | 11110000\ 83 | 11110000\ 84 | 11110000\ 85 | 11110000\ 86 | 11110000\ 87 | 11110000\ 88 | 11110000, 89 | 90 | 0b\ 91 | 00001111\ 92 | 00001111\ 93 | 00001111\ 94 | 00001111\ 95 | 11110000\ 96 | 11110000\ 97 | 11110000\ 98 | 11110000, 99 | 100 | 0b\ 101 | 11111111\ 102 | 11111111\ 103 | 11111111\ 104 | 11111111\ 105 | 11110000\ 106 | 11110000\ 107 | 11110000\ 108 | 11110000, 109 | 110 | 111 | 0b\ 112 | 00000000\ 113 | 00000000\ 114 | 00000000\ 115 | 00000000\ 116 | 00001111\ 117 | 00001111\ 118 | 00001111\ 119 | 00001111, 120 | 121 | 0b\ 122 | 11110000\ 123 | 11110000\ 124 | 11110000\ 125 | 11110000\ 126 | 00001111\ 127 | 00001111\ 128 | 00001111\ 129 | 00001111, 130 | 131 | 0b\ 132 | 00001111\ 133 | 00001111\ 134 | 00001111\ 135 | 00001111\ 136 | 00001111\ 137 | 00001111\ 138 | 00001111\ 139 | 00001111, 140 | 141 | 0b\ 142 | 11111111\ 143 | 11111111\ 144 | 11111111\ 145 | 11111111\ 146 | 00001111\ 147 | 00001111\ 148 | 00001111\ 149 | 00001111, 150 | 151 | 0b\ 152 | 00000000\ 153 | 00000000\ 154 | 00000000\ 155 | 00000000\ 156 | 11111111\ 157 | 11111111\ 158 | 11111111\ 159 | 11111111, 160 | 161 | 0b\ 162 | 11110000\ 163 | 11110000\ 164 | 11110000\ 165 | 11110000\ 166 | 11111111\ 167 | 11111111\ 168 | 11111111\ 169 | 11111111, 170 | 171 | 0b\ 172 | 00001111\ 173 | 00001111\ 174 | 00001111\ 175 | 00001111\ 176 | 11111111\ 177 | 11111111\ 178 | 11111111\ 179 | 11111111, 180 | 181 | 0b\ 182 | 11111111\ 183 | 11111111\ 184 | 11111111\ 185 | 11111111\ 186 | 11111111\ 187 | 11111111\ 188 | 11111111\ 189 | 11111111, 190 | 191 | 192 | /****/ 193 | 194 | 0b\ 195 | 10000000\ 196 | 10000000\ 197 | 10000000\ 198 | 10000000\ 199 | 10000000\ 200 | 10000000\ 201 | 10000000\ 202 | 10000000, 203 | 204 | 0b\ 205 | 11000000\ 206 | 11000000\ 207 | 11000000\ 208 | 11000000\ 209 | 11000000\ 210 | 11000000\ 211 | 11000000\ 212 | 11000000, 213 | 214 | 0b\ 215 | 11100000\ 216 | 11100000\ 217 | 11100000\ 218 | 11100000\ 219 | 11100000\ 220 | 11100000\ 221 | 11100000\ 222 | 11100000, 223 | 224 | 0b\ 225 | 11111000\ 226 | 11111000\ 227 | 11111000\ 228 | 11111000\ 229 | 11111000\ 230 | 11111000\ 231 | 11111000\ 232 | 11111000, 233 | 234 | 0b\ 235 | 11111100\ 236 | 11111100\ 237 | 11111100\ 238 | 11111100\ 239 | 11111100\ 240 | 11111100\ 241 | 11111100\ 242 | 11111100, 243 | 244 | 0b\ 245 | 11111110\ 246 | 11111110\ 247 | 11111110\ 248 | 11111110\ 249 | 11111110\ 250 | 11111110\ 251 | 11111110\ 252 | 11111110, 253 | 254 | 0b\ 255 | 00000000\ 256 | 00000000\ 257 | 00000000\ 258 | 00000000\ 259 | 00000000\ 260 | 00000000\ 261 | 00000000\ 262 | 11111111, 263 | 264 | 0b\ 265 | 00000000\ 266 | 00000000\ 267 | 00000000\ 268 | 00000000\ 269 | 00000000\ 270 | 00000000\ 271 | 11111111\ 272 | 11111111, 273 | 274 | 0b\ 275 | 00000000\ 276 | 00000000\ 277 | 00000000\ 278 | 00000000\ 279 | 00000000\ 280 | 11111111\ 281 | 11111111\ 282 | 11111111, 283 | 284 | 0b\ 285 | 00000000\ 286 | 00000000\ 287 | 00000000\ 288 | 11111111\ 289 | 11111111\ 290 | 11111111\ 291 | 11111111\ 292 | 11111111, 293 | 294 | 0b\ 295 | 00000000\ 296 | 00000000\ 297 | 11111111\ 298 | 11111111\ 299 | 11111111\ 300 | 11111111\ 301 | 11111111\ 302 | 11111111, 303 | 304 | 0b\ 305 | 00000000\ 306 | 11111111\ 307 | 11111111\ 308 | 11111111\ 309 | 11111111\ 310 | 11111111\ 311 | 11111111\ 312 | 11111111, 313 | 314 | 0b\ 315 | 00000000\ 316 | 00000000\ 317 | 00111100\ 318 | 00111100\ 319 | 00111100\ 320 | 00111100\ 321 | 00000000\ 322 | 00000000, 323 | 324 | 325 | 0b\ 326 | 00000000\ 327 | 00000000\ 328 | 00000000\ 329 | 11111111\ 330 | 11111111\ 331 | 00000000\ 332 | 00000000\ 333 | 00000000, 334 | 335 | 336 | 0b\ 337 | 00011000\ 338 | 00011000\ 339 | 00011000\ 340 | 00011000\ 341 | 00011000\ 342 | 00011000\ 343 | 00011000\ 344 | 00011000, 345 | 346 | 0b\ 347 | 00011000\ 348 | 00011000\ 349 | 00011000\ 350 | 11111111\ 351 | 11111111\ 352 | 00011000\ 353 | 00011000\ 354 | 00011000, 355 | 356 | /****/ 357 | 358 | 0b\ 359 | 01111111\ 360 | 01111111\ 361 | 01111111\ 362 | 01111111\ 363 | 01111111\ 364 | 01111111\ 365 | 01111111\ 366 | 01111111, 367 | 368 | 0b\ 369 | 00111111\ 370 | 00111111\ 371 | 00111111\ 372 | 00111111\ 373 | 00111111\ 374 | 00111111\ 375 | 00111111\ 376 | 00111111, 377 | 378 | 0b\ 379 | 00011111\ 380 | 00011111\ 381 | 00011111\ 382 | 00011111\ 383 | 00011111\ 384 | 00011111\ 385 | 00011111\ 386 | 00011111, 387 | 388 | 0b\ 389 | 00000111\ 390 | 00000111\ 391 | 00000111\ 392 | 00000111\ 393 | 00000111\ 394 | 00000111\ 395 | 00000111\ 396 | 00000111, 397 | 398 | 0b\ 399 | 00000011\ 400 | 00000011\ 401 | 00000011\ 402 | 00000011\ 403 | 00000011\ 404 | 00000011\ 405 | 00000011\ 406 | 00000011, 407 | 408 | 0b\ 409 | 00000001\ 410 | 00000001\ 411 | 00000001\ 412 | 00000001\ 413 | 00000001\ 414 | 00000001\ 415 | 00000001\ 416 | 00000001, 417 | 418 | 0b\ 419 | 11111111\ 420 | 11111111\ 421 | 11111111\ 422 | 11111111\ 423 | 11111111\ 424 | 11111111\ 425 | 11111111\ 426 | 00000000, 427 | 428 | 0b\ 429 | 11111111\ 430 | 11111111\ 431 | 11111111\ 432 | 11111111\ 433 | 11111111\ 434 | 11111111\ 435 | 00000000\ 436 | 00000000, 437 | 438 | 0b\ 439 | 11111111\ 440 | 11111111\ 441 | 11111111\ 442 | 11111111\ 443 | 11111111\ 444 | 00000000\ 445 | 00000000\ 446 | 00000000, 447 | 448 | 0b\ 449 | 11111111\ 450 | 11111111\ 451 | 11111111\ 452 | 00000000\ 453 | 00000000\ 454 | 00000000\ 455 | 00000000\ 456 | 00000000, 457 | 458 | 0b\ 459 | 11111111\ 460 | 11111111\ 461 | 00000000\ 462 | 00000000\ 463 | 00000000\ 464 | 00000000\ 465 | 00000000\ 466 | 00000000, 467 | 468 | 0b\ 469 | 11111111\ 470 | 00000000\ 471 | 00000000\ 472 | 00000000\ 473 | 00000000\ 474 | 00000000\ 475 | 00000000\ 476 | 00000000, 477 | 478 | 0b\ 479 | 11111111\ 480 | 11111111\ 481 | 11000011\ 482 | 11000011\ 483 | 11000011\ 484 | 11000011\ 485 | 11111111\ 486 | 11111111, 487 | 488 | 0b\ 489 | 11111111\ 490 | 11111111\ 491 | 11111111\ 492 | 00000000\ 493 | 00000000\ 494 | 11111111\ 495 | 11111111\ 496 | 11111111, 497 | 498 | 0b\ 499 | 11100111\ 500 | 11100111\ 501 | 11100111\ 502 | 11100111\ 503 | 11100111\ 504 | 11100111\ 505 | 11100111\ 506 | 11100111, 507 | 508 | 0b\ 509 | 11100111\ 510 | 11100111\ 511 | 11100111\ 512 | 00000000\ 513 | 00000000\ 514 | 11100111\ 515 | 11100111\ 516 | 11100111, 517 | }; 518 | 519 | 520 | static float rowerror[2000][3]; 521 | static float error[3]; 522 | 523 | static int do_dither = 0; 524 | static int cfg_mono = 0; 525 | static float cfg_crisp = 0.1; 526 | 527 | void set_gray (Nchanterm *n, int x, int y, float value) 528 | { 529 | int i = value * 5.0; 530 | if (i > 5) 531 | i = 5; 532 | if (i < 0) 533 | i = 0; 534 | nct_set (n, x, y, utf8_gray_scale[i]); 535 | } 536 | 537 | /* draws with 4 input subpixels per output subpixel, the routine is 538 | * written to deal with an input that has 2x2 input pixels per 539 | * output pixel. 540 | */ 541 | static void draw_rgb_cell (Nchanterm *n, int x, int y, 542 | float r[64], float g[64], float b[64], 543 | int dither, float crispness) 544 | { 545 | float sum[3] = {0,0,0}; 546 | int i; 547 | for (i=0; i<64; i++) 548 | { 549 | sum[0] += r[i]/64; 550 | sum[1] += g[i]/64; 551 | sum[2] += b[i]/64; 552 | } 553 | if (dither) 554 | { 555 | int i; 556 | for (i = 0; i < 3; i++) 557 | { 558 | sum[i]+=error[i]; 559 | sum[i]+=rowerror[x][i]; 560 | rowerror[x][i]=0; 561 | } 562 | } 563 | { 564 | /* go through all fg/bg combinations with all color mixtures and 565 | * compare with our desired average color output 566 | */ 567 | int fg, bg; 568 | float mix; 569 | 570 | int best_fg = 7; 571 | int best_bg = 0; 572 | float best_mix = 1.0; 573 | float best_distance = 10000000.0; 574 | float best_rgb[3]; 575 | int use_geom = 0; 576 | 577 | #define POW2(a) ((a)*(a)) 578 | 579 | for (fg = 0; fg < 8; fg ++) 580 | for (bg = 0; bg < 8; bg ++) 581 | { 582 | for (mix = 0.0; mix <= 1.0; mix+=0.25) 583 | { 584 | int frgb[3] = { (fg & 1)!=0, 585 | (fg & 2)!=0, 586 | (fg & 4)!=0}; 587 | int brgb[3] = { (bg & 1)!=0, 588 | (bg & 2)!=0, 589 | (bg & 4)!=0}; 590 | float resrgb[3]; 591 | float distance; 592 | int c; 593 | for (c = 0; c < 3; c++) 594 | resrgb[c] = (frgb[c] * mix + brgb[c] * (1.0-mix)) ; 595 | 596 | distance = sqrt (POW2(resrgb[0] - sum[0])+ 597 | POW2(resrgb[1] - sum[1])+ 598 | POW2(resrgb[2] - sum[2])); 599 | if (distance <= best_distance) 600 | { 601 | int i; 602 | best_distance = distance; 603 | best_fg = fg; 604 | best_bg = bg; 605 | best_mix = mix; 606 | for (i = 0; i<3; i++) 607 | best_rgb[i] = resrgb[i]; 608 | } 609 | } 610 | } 611 | /* change to other equivalents that seems to behave better than 612 | * their correspondent 613 | */ 614 | if (best_bg == 7 && best_fg == 7) 615 | { 616 | best_fg = 7; 617 | best_bg = 0; 618 | best_mix = 1.0; 619 | } 620 | if (best_fg == 0 && best_mix >=1.0 ) 621 | { 622 | best_bg = 0; 623 | best_fg = 7; 624 | best_mix = 0.0; 625 | } 626 | 627 | int bestbits = 0; 628 | { 629 | int totbits; 630 | for (totbits = 0; quarter_blocks[totbits]; totbits++) 631 | { 632 | float br[64],bg[64],bb[64]; 633 | int i, x, y; 634 | float distance = 0; 635 | 636 | for (i = 0, y = 0; y < 8; y ++) 637 | for (x = 0; x < 8; x ++, i++) 638 | { 639 | int set = (images[totbits] >> ((7-y) * 8 + (7-x) )) & 1; 640 | br[i] = set ? ((best_fg & 1) != 0) : ((best_bg & 1) != 0); 641 | bg[i] = set ? ((best_fg & 2) != 0) : ((best_bg & 2) != 0); 642 | bb[i] = set ? ((best_fg & 4) != 0) : ((best_bg & 4) != 0); 643 | } 644 | 645 | for (i=0;i<64;i++) 646 | distance += sqrt (POW2(br[i] - r[i])+ 647 | POW2(bg[i] - g[i])+ 648 | POW2(bb[i] - b[i])); 649 | 650 | float GEOM_FACTOR = (1.000001 - crispness) / 3; 651 | 652 | distance = distance * GEOM_FACTOR / 64; 653 | 654 | if (distance <= best_distance) 655 | { 656 | best_distance = distance; 657 | use_geom = 1; 658 | bestbits = totbits; 659 | } 660 | } 661 | } 662 | if (use_geom && bestbits >= 31) /* one of the patterns matching so that it 663 | needs inverted fg/bg */ 664 | { 665 | int tmp = best_fg; 666 | best_fg = best_bg; 667 | best_bg = tmp; 668 | } 669 | 670 | if (dither) 671 | { 672 | int i; 673 | 674 | if (use_geom) 675 | { 676 | int bits_set = 0; 677 | float mix; 678 | int frgb[3] = { (best_fg & 1)!=0, 679 | (best_fg & 2)!=0, 680 | (best_fg & 4)!=0}; 681 | int brgb[3] = { (best_bg & 1)!=0, 682 | (best_bg & 2)!=0, 683 | (best_bg & 4)!=0}; 684 | for (i = 0; i < 64; i++) 685 | if ((images[bestbits] >> i) & 1) 686 | bits_set ++; 687 | 688 | mix = (bits_set / 64.0); 689 | 690 | for (i = 0; i < 3; i++) 691 | { 692 | best_rgb[i] = (frgb[i] * mix + brgb[i] * (1.0-mix)) ; 693 | } 694 | } 695 | 696 | for (i = 0; i < 3; i++) 697 | { 698 | float delta = (sum[i] - best_rgb[i]); 699 | error[i] = delta * 0.20; 700 | rowerror[x][i] += delta * 0.20; 701 | if (x>0) 702 | rowerror[x-1][i] += delta * 0.20; 703 | rowerror[x+1][i] += delta * 0.20; 704 | } 705 | } 706 | 707 | nct_fg_color (n, best_fg); 708 | nct_bg_color (n, best_bg); 709 | 710 | if (use_geom) 711 | nct_set (n, x, y, quarter_blocks[bestbits]); 712 | else 713 | set_gray (n, x, y, best_mix); 714 | } 715 | } 716 | 717 | /* draw a 32bit RGBA file,.. 718 | */ 719 | void nct_buf (Nchanterm *n, int x0, int y0, int w, int h, 720 | unsigned char *buf, int rw, int rh, 721 | int dither, float crispness, int mono) 722 | { 723 | int u, v; 724 | 725 | if (!buf) 726 | return; 727 | 728 | memset (rowerror, 0, sizeof (rowerror)); 729 | memset (error, 0, sizeof (error)); 730 | 731 | for (v = 0; v < h; v++) 732 | for (u = 0; u < w; u++) 733 | { 734 | float r[64], g[64], b[64]; 735 | float xo, yo; 736 | int no = 0; 737 | for (yo = 0.0; yo <= 0.875; yo += 0.125) 738 | for (xo = 0.0; xo <= 0.875; xo += 0.125, no++) 739 | { 740 | int c = 0; 741 | 742 | /* do a set of samplings to get a crude higher 743 | * quality box down-sampler?, do this crunching 744 | * based on a scaling factor. 745 | */ 746 | 747 | float uo = 0.0, vo = 0.0; 748 | r[no]=g[no]=b[no]=0.0; 749 | for (uo = 0.0 ; uo <= 0.5; uo+= 0.2) /* these increments should be adjusted */ 750 | for (vo = 0.0 ; vo <= 0.5; vo+= 0.2) /* according to scaling factor */ 751 | { 752 | int x, y; 753 | x = ((u+xo+uo) * 1.0 / w) * rw; 754 | y = ((v+yo+vo) * 1.0 / h) * rh; 755 | if (x<0) x = 0; 756 | if (y<0) y = 0; 757 | if (x>=rw) x = rw-1; 758 | if (y>=rh) y = rh-1; 759 | 760 | r[no] += buf [(y * rw + x) * 4 + 0] / 255.0; 761 | g[no] += buf [(y * rw + x) * 4 + 1] / 255.0; 762 | b[no] += buf [(y * rw + x) * 4 + 2] / 255.0; 763 | c++; 764 | } 765 | r[no] /= c; 766 | g[no] /= c; 767 | b[no] /= c; 768 | if (mono) 769 | { 770 | float v = (r[no] + g[no] + b[no])/3; 771 | r[no] = g[no] = b[no] = v; 772 | } 773 | } 774 | draw_rgb_cell (n, x0+u, y0+v, r, g, b, dither, crispness); 775 | } 776 | } 777 | 778 | void nct_set_dither (Nchanterm *n, int dither) 779 | { 780 | do_dither = dither; 781 | } 782 | 783 | void nct_set_crispness (Nchanterm *n, float crispness) 784 | { 785 | cfg_crisp = crispness; 786 | } 787 | 788 | void nct_set_mono (Nchanterm *n, int mono) 789 | { 790 | cfg_mono = mono; 791 | } 792 | 793 | void nct_image (Nchanterm *n, int x0, int y0, int w, int h, const char *path) 794 | { 795 | int rw, rh; 796 | unsigned char *image= NULL; 797 | image = stbi_load (path, &rw, &rh, NULL, 4); 798 | if (!image) 799 | return; 800 | nct_buf (n, x0, y0, w, h, image, rw, rh, do_dither, cfg_crisp, cfg_mono); 801 | free (image); 802 | } 803 | 804 | int main (int argc, char **argv) 805 | { 806 | Nchanterm *term; 807 | int quit = 0; 808 | 809 | if (!argv[1]) 810 | { 811 | printf ("usage:\nnchantview \n"); 812 | return -1; 813 | } 814 | 815 | if (argv[2]) 816 | { 817 | nct_set_dither (NULL, atoi(argv[2])); 818 | if (argv[3]) 819 | { 820 | nct_set_crispness (NULL, atof(argv[3])); 821 | if (argv[4]) 822 | nct_set_mono (NULL, 1); 823 | } 824 | } 825 | 826 | 827 | term = nct_new (); 828 | nct_clear (term); 829 | nct_image (term, 1, 1, nct_width (term), nct_height (term), argv[1]); 830 | nct_flush (term); 831 | 832 | while (!quit) 833 | { 834 | const char *event; 835 | event = nct_get_event (term, 1000, NULL, NULL); 836 | if (!strcmp (event, "control-c")|| 837 | !strcmp (event, "esc")) 838 | quit = 1; 839 | else if (!strcmp (event, "size-changed")) 840 | { 841 | nct_set_size (term, nct_sys_terminal_width (), 842 | nct_sys_terminal_height ()); 843 | nct_clear (term); 844 | nct_image (term, 1, 1, nct_width (term), nct_height (term), argv[1]); 845 | nct_flush (term); 846 | } 847 | else if (!strcmp (event, "control-l")) 848 | nct_reflush (term); 849 | } 850 | nct_destroy (term); 851 | return 0; 852 | } 853 | -------------------------------------------------------------------------------- /examples/test-include.c: -------------------------------------------------------------------------------- 1 | #include /* sprintf */ 2 | 3 | #include "nchanterm.c" 4 | 5 | int main (int argc, char **argv) 6 | { 7 | Nchanterm *term = nct_new (); 8 | const char *event = ""; 9 | int ex = 0, ey = 0; 10 | int quit = 0; 11 | 12 | nct_mouse (term, NC_MOUSE_DRAG); 13 | while (!quit) 14 | { 15 | nct_flush (term); 16 | 17 | do 18 | { 19 | event = nct_get_event (term, 5000, &ex, &ey); 20 | 21 | if (!strcmp (event, "control-c")|| 22 | !strcmp (event, "esc")) 23 | quit = 1; 24 | else if (!strcmp (event, "size-changed")) 25 | nct_set_size (term, nct_sys_terminal_width (), 26 | nct_sys_terminal_height ()); 27 | else if (!strcmp (event, "control-l")) 28 | { 29 | event = "forced redraw"; 30 | nct_reflush (term); 31 | } 32 | 33 | 34 | int x = 3 , y = 3; 35 | nct_clear (term); 36 | 37 | x += nct_print (term, x, y, "event:", -1); 38 | x ++; 39 | x += nct_print (term, x, y, event, -1); 40 | 41 | if (ex >0) { 42 | char buf[100]; 43 | sprintf (buf, "%i %i", ex, ey); 44 | x += nct_print (term, x, y, buf, -1); 45 | } 46 | } 47 | while (nct_has_event (term, 0)); 48 | } 49 | nct_destroy (term); 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /examples/test.c: -------------------------------------------------------------------------------- 1 | #include /* sprintf */ 2 | #include 3 | 4 | #define NCHANTERM_HEADER_ONLY 5 | #include "nchanterm.c" 6 | 7 | #define NL do {x = 2; y++;}while(0) 8 | #define P(string) do {x += nct_print (term, x, y, string, -1);}while(0) 9 | 10 | int main (int argc, char **argv) 11 | { 12 | Nchanterm *term = nct_new (); 13 | const char *event = ""; 14 | int ex = 0, ey = 0; 15 | int quit = 0; 16 | 17 | nct_mouse (term, NC_MOUSE_DRAG); 18 | while (!quit) 19 | { 20 | int x = 1 , y = 1; 21 | nct_clear (term); 22 | 23 | NL; 24 | P ("This is a tiny terminal abstraction"); 25 | NL; 26 | NL; 27 | nct_set_attr (term, NCT_A_REVERSE); 28 | P ("event"); 29 | x+=2; 30 | nct_set_attr (term, NCT_A_NORMAL); 31 | P (event); 32 | x++; 33 | if (ex >0) { 34 | char buf[100]; 35 | sprintf (buf, "%i %i", ex, ey); 36 | P(buf); 37 | } 38 | NL; 39 | NL; 40 | 41 | 42 | nct_set_attr (term, NCT_A_BOLD);P("bold"); 43 | nct_set_attr (term, NCT_A_UNDERLINE);P("underline"); 44 | nct_set_attr (term, NCT_A_REVERSE);P("reverse"); 45 | nct_set_attr (term, NCT_A_DIM);P("dim"); 46 | 47 | NL; 48 | NL; 49 | 50 | nct_set_attr (term, NCT_A_DIM); 51 | { /* draw all fg/bg combinations */ 52 | int fg, bg; 53 | for (fg = 0; fg < 8; fg++) 54 | for (bg = 0; bg < 8; bg++) 55 | { 56 | nct_fg_color (term, fg); 57 | nct_bg_color (term, bg); 58 | nct_set (term, fg+3 + 9 * 0, bg+y, "X"); 59 | } 60 | } 61 | nct_set_attr (term, NCT_A_NORMAL); 62 | { /* draw all fg/bg combinations */ 63 | int fg, bg; 64 | for (fg = 0; fg < 8; fg++) 65 | for (bg = 0; bg < 8; bg++) 66 | { 67 | nct_fg_color (term, fg); 68 | nct_bg_color (term, bg); 69 | nct_set (term, fg+3 + 9 * 1, bg+y, "X"); 70 | } 71 | } 72 | nct_set_attr (term, NCT_A_BOLD); 73 | { /* draw all fg/bg combinations */ 74 | int fg, bg; 75 | for (fg = 0; fg < 8; fg++) 76 | for (bg = 0; bg < 8; bg++) 77 | { 78 | nct_fg_color (term, fg); 79 | nct_bg_color (term, bg); 80 | nct_set (term, fg+3 + 9 * 2, bg+y, "X"); 81 | } 82 | } 83 | nct_flush (term); 84 | 85 | do 86 | { 87 | event = nct_get_event (term, 5000, &ex, &ey); 88 | 89 | if (!strcmp (event, "control-c")|| 90 | !strcmp (event, "esc")) 91 | quit = 1; 92 | else if (!strcmp (event, "size-changed")) 93 | nct_set_size (term, nct_sys_terminal_width (), 94 | nct_sys_terminal_height ()); 95 | else if (!strcmp (event, "control-l")) 96 | { 97 | event = "forced redraw"; 98 | nct_reflush (term); 99 | } 100 | } while (nct_has_event (term, 15)); 101 | } 102 | nct_destroy (term); 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /nchanterm.c: -------------------------------------------------------------------------------- 1 | /* nchanterm, a minimal ANSI based utf8 terminal control abstraction. 2 | * 3 | * Copyright (c) 2012 Øyvind Kolås 4 | * 5 | * Permission to use, copy, modify, and/or distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #ifndef NCHANTERM_H 19 | #define NCHANTERM_H 20 | 21 | typedef struct _Nchanterm Nchanterm; 22 | 23 | /* create a new terminal instance, automatically sized to initial size 24 | * of terminal. 25 | */ 26 | Nchanterm *nct_new (void); 27 | /* free up a terminal instance */ 28 | void nct_destroy (Nchanterm *term); 29 | /* set the size of the terminal, you need to do this in resize-callbacks */ 30 | void nct_set_size (Nchanterm *term, int width, int height); 31 | /* query size of terminal */ 32 | int nct_width (Nchanterm *term); 33 | int nct_height (Nchanterm *term); 34 | /* clear terminal contents to ' ', reset attributes and colors */ 35 | void nct_clear (Nchanterm *term); 36 | /* set a utf8 char from the buffer str at location x, y with current style */ 37 | void nct_set (Nchanterm *term, int x, int y, const char *str); 38 | /* read back the utf8 string in a character cell (XXX: style readback missing)*/ 39 | const char *nct_get (Nchanterm *term, int x, int y); 40 | /* print a string at given coordinates, only num_chars first chars printed */ 41 | int nct_print (Nchanterm *term, int x, int y, 42 | const char *string, int num_chars); 43 | enum { 44 | NCT_COLOR_BLACK = 0, 45 | NCT_COLOR_RED = 1, 46 | NCT_COLOR_GREEN = 2, 47 | NCT_COLOR_YELLOW = 3, 48 | NCT_COLOR_BLUE = 4, 49 | NCT_COLOR_MAGENTA= 5, 50 | NCT_COLOR_CYAN = 6, 51 | NCT_COLOR_WHITE = 7 52 | }; 53 | /* set the foreground/background color used */ 54 | void nct_fg_color (Nchanterm *term, int color); 55 | void nct_bg_color (Nchanterm *term, int color); 56 | 57 | enum { 58 | NCT_A_NORMAL = 0, 59 | NCT_A_BOLD = 1 << 0, 60 | NCT_A_DIM = 1 << 1, 61 | NCT_A_UNDERLINE = 1 << 2, 62 | NCT_A_REVERSE = 1 << 3, 63 | }; 64 | 65 | /* set the attribute mode, this is a bitmask combination of the above modes. */ 66 | void nct_set_attr (Nchanterm *term, int attr); 67 | /* get current attribute mode. */ 68 | int nct_get_attr (Nchanterm *term); 69 | 70 | /* update what is visible to the user */ 71 | void nct_flush (Nchanterm *term); 72 | 73 | /* force a full redraw */ 74 | void nct_reflush (Nchanterm *term); 75 | 76 | /* get the width and height of the terminal querying the system */ 77 | int nct_sys_terminal_width (void); 78 | int nct_sys_terminal_height (void); 79 | 80 | /* toggle visibility of the cursor */ 81 | void nct_show_cursor (Nchanterm *term); 82 | void nct_hide_cursor (Nchanterm *term); 83 | 84 | /* set position of cursor */ 85 | void nct_set_cursor_pos (Nchanterm *term, int x, int y); 86 | 87 | /* get position of cursor */ 88 | void nct_get_cursor_pos (Nchanterm *term, int *x, int *y); 89 | 90 | /* input handling 91 | * nchanteds input handling reports keyboard, window resizing and mouse 92 | * events in a uniform string based API. UTF8 input is passed through 93 | * as the strings of the glyphs as the event string. 94 | * 95 | * "size-changed" - the geometry of the terminal has changed 96 | * "mouse-pressed" - the left mouse button was clicked 97 | * "mouse-drag" - the left mouse button was clicked 98 | * "mouse-motion" - motion events (also after release) 99 | * "idle" - timeout elapsed. 100 | * keys: "up" "down" "space" "control-left" "return" "esc" "a" "A" 101 | * "control-a" "F1" "control-c" ... 102 | */ 103 | 104 | /* get an event as a string from the terminal, if it is a mouse event coords 105 | * will be returned in x and y if not NULL, block for up to timeout_ms */ 106 | const char *nct_get_event (Nchanterm *n, int timeout_ms, int *x, int *y); 107 | 108 | /* check if there is pending events, with a timeout */ 109 | int nct_has_event (Nchanterm *n, int timeout_ms); 110 | 111 | /* get a human readable/suited for ui string representing a keybinding */ 112 | const char *nct_key_get_label (Nchanterm *n, const char *nick); 113 | 114 | enum { 115 | NC_MOUSE_NONE = 0, 116 | NC_MOUSE_PRESS = 1, /* "mouse-pressed", "mouse-released" */ 117 | NC_MOUSE_DRAG = 2, /* + "mouse-drag" (motion with pressed button) */ 118 | NC_MOUSE_ALL = 3 /* + "mouse-motion" (also delivered for release) */ 119 | }; 120 | /* set which mouse events to report, defaults to report none */ 121 | void nct_mouse (Nchanterm *term, int nct_mouse_state); 122 | #endif 123 | 124 | /************************** end of header ***********************/ 125 | #ifndef NCHANTERM_HEADER_ONLY 126 | 127 | #include 128 | #include 129 | #include 130 | #include 131 | #include 132 | #include 133 | #include 134 | #include 135 | #include 136 | #include 137 | 138 | int nct_sys_terminal_width (void) 139 | { 140 | struct winsize ws; 141 | if (ioctl(0,TIOCGWINSZ,&ws)!=0) 142 | return 80; 143 | return ws.ws_col; 144 | } 145 | 146 | int nct_sys_terminal_height (void) 147 | { 148 | struct winsize ws; 149 | if (ioctl(0,TIOCGWINSZ,&ws)!=0) 150 | return 25; 151 | return ws.ws_row; 152 | } 153 | 154 | typedef struct NcCell 155 | { 156 | char str[8]; /* utf8 representation of char */ 157 | int attr; /* attributes */ 158 | int color; /* both fg and bg */ 159 | } NcCell; 160 | 161 | struct _Nchanterm { 162 | NcCell *cells_front; 163 | NcCell *cells_back; 164 | int cells_width; 165 | int cells_height; 166 | int mode; 167 | int color; 168 | int mode_set; 169 | int color_set; 170 | int width; 171 | int height; 172 | int cursor_x; 173 | int cursor_y; 174 | float mouse_x; 175 | float mouse_y; 176 | int mouse_fd; 177 | int utf8; 178 | int is_st; 179 | }; 180 | 181 | /* a quite minimal core set of terminal escape sequences are used to do all 182 | * things nchanterm can do */ 183 | #define ANSI_RESET_DEVICE "\033c" 184 | #define ANSI_YX "\033[%d;%dH" 185 | #define ANSI_CURSOR_FORWARD "\033[%dC" 186 | #define ANSI_CURSOR_FORWARD1 "\033[C" 187 | #define ANSI_STYLE_RESET "\033[m" 188 | #define ANSI_STYLE_START "\033[" 189 | #define ANSI_STYLE_END "m" 190 | #define DECTCEM_CURSOR_SHOW "\033[?25h" 191 | #define DECTCEM_CURSOR_HIDE "\033[?25l" 192 | #define TERMINAL_MOUSE_OFF "\033[?1000l" 193 | #define TERMINAL_MOUSE_ON_BASIC "\033[?1000h" 194 | #define TERMINAL_MOUSE_ON_DRAG "\033[?1000h\033[?1003h" /* +ON_BASIC for wider */ 195 | #define TERMINAL_MOUSE_ON_FULL "\033[?1000h\033[?1004h" /* compatibility */ 196 | #define XTERM_ALTSCREEN_ON "\033[?47h" 197 | #define XTERM_ALTSCREEN_OFF "\033[?47l" 198 | 199 | #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) 200 | #define UNUSED __attribute__((__unused__)) 201 | #else 202 | #define UNUSED 203 | #endif 204 | 205 | void nct_show_cursor (Nchanterm UNUSED *t) 206 | { 207 | printf ("%s", DECTCEM_CURSOR_SHOW); 208 | } 209 | void nct_hide_cursor (Nchanterm UNUSED *t) 210 | { 211 | printf ("%s", DECTCEM_CURSOR_HIDE); 212 | } 213 | 214 | static int nct_utf8_len (const unsigned char first_byte) 215 | { 216 | if ((first_byte & 0x80) == 0) 217 | return 1; /* ASCII */ 218 | else if ((first_byte & 0xE0) == 0xC0) 219 | return 2; 220 | else if ((first_byte & 0xF0) == 0xE0) 221 | return 3; 222 | else if ((first_byte & 0xF8) == 0xF0) 223 | return 4; 224 | return 1; 225 | } 226 | 227 | static int nct_utf8_strlen (const char *s) 228 | { 229 | int count; 230 | if (!s) 231 | return 0; 232 | for (count = 0; *s; s++) 233 | if ((*s & 0xC0) != 0x80) 234 | count++; 235 | return count; 236 | } 237 | 238 | void nct_set_size (Nchanterm *n, int width, int height) 239 | { 240 | n->width = width; 241 | n->height = height; 242 | nct_set_cursor_pos (n, width, height); 243 | } 244 | 245 | int nct_width (Nchanterm *n) 246 | { 247 | return n->width; 248 | } 249 | int nct_height (Nchanterm *n) 250 | { 251 | return n->height; 252 | } 253 | 254 | #define NCT_COLOR_PAIR(f,b) (f * 8 + b) 255 | #define NCT_COLOR_FG(p) (((int)p)/8) 256 | #define NCT_COLOR_BG(p) (((int)p)%8) 257 | #define NCHANT_DEFAULT_COLORS (NCT_COLOR_PAIR(NCT_COLOR_WHITE, NCT_COLOR_BLACK)) 258 | 259 | /* emit minimal(few at least) escape characters to bring terminal from known 260 | * current mode to desired mode. */ 261 | static void nct_ensure_mode (Nchanterm *n) 262 | { 263 | int first = 1; 264 | int data = 0; 265 | 266 | if (n->mode_set == n->mode && n->color_set == n->color) 267 | return; 268 | n->mode_set = n->mode; 269 | n->color_set = n->color; 270 | 271 | printf(ANSI_STYLE_RESET); 272 | #define SEPERATOR { if (!data) { printf(ANSI_STYLE_START); data = 1;} \ 273 | if (first) first=0; else printf (";");} 274 | if (n->mode & NCT_A_BOLD) { SEPERATOR; printf ("1"); } 275 | if ((n->mode & NCT_A_DIM) && !n->is_st) { SEPERATOR; printf ("2");} 276 | if (n->mode & NCT_A_REVERSE) { SEPERATOR; printf ("7"); } 277 | if (n->mode & NCT_A_UNDERLINE){ SEPERATOR; printf ("4"); } 278 | if (n->color != NCHANT_DEFAULT_COLORS) 279 | { SEPERATOR; 280 | if (NCT_COLOR_BG(n->color) == 0) 281 | printf ("%i", NCT_COLOR_FG(n->color)+30); 282 | else 283 | printf ("%i;%i", NCT_COLOR_FG(n->color)+30, 284 | NCT_COLOR_BG(n->color)+40); } 285 | if (data) 286 | printf(ANSI_STYLE_END); 287 | #undef SEPERATOR 288 | } 289 | 290 | void nct_set_attr (Nchanterm *n, int attr) 291 | { 292 | if (n->mode != attr) 293 | n->mode = attr; 294 | } 295 | 296 | int nct_get_attr (Nchanterm *n) 297 | { 298 | return n->mode; 299 | } 300 | 301 | void nct_fg_color (Nchanterm *n, int ncolor) 302 | { 303 | n->color -= NCT_COLOR_FG (n->color) * 8; 304 | n->color += ncolor * 8; 305 | } 306 | 307 | void nct_bg_color (Nchanterm *n, int ncolor) 308 | { 309 | n->color -= NCT_COLOR_BG (n->color); 310 | n->color += ncolor; 311 | } 312 | 313 | int nct_print (Nchanterm *n, int x, int y, const char *string, int utf8_length) 314 | { 315 | const char *s; 316 | int len; 317 | int pos = 0; 318 | if (!string) 319 | return 0; 320 | if (utf8_length < 0) 321 | utf8_length = nct_utf8_strlen (string); 322 | 323 | for (s = string; pos < utf8_length && *s; s += nct_utf8_len (*s)) 324 | { 325 | int c; 326 | len = nct_utf8_len (*s); 327 | nct_set (n, x++, y, s); 328 | for (c = 0; c < len; c++) 329 | if (!s[c]) 330 | return pos; 331 | pos++; 332 | } 333 | return pos; 334 | } 335 | 336 | static void nct_cells_clear (Nchanterm *n) 337 | { 338 | int i; 339 | for (i = 0; i < n->cells_width * n->cells_height; i ++) 340 | { 341 | n->cells_back[i].str[0]=' '; 342 | n->cells_back[i].str[1]='\0'; 343 | n->cells_back[i].attr = 0; 344 | n->cells_back[i].color = NCHANT_DEFAULT_COLORS; 345 | } 346 | } 347 | 348 | void nct_clear (Nchanterm *n) 349 | { 350 | n->color = NCHANT_DEFAULT_COLORS; 351 | nct_set_attr (n, NCT_A_NORMAL); 352 | nct_cells_clear (n); 353 | } 354 | 355 | static void nct_cells_clear_front (Nchanterm *n) 356 | { 357 | int i; 358 | for (i = 0; i < n->cells_width * n->cells_height; i ++) 359 | n->cells_front[i].str[0]='\2'; 360 | } 361 | 362 | static void nct_cells_ensure (Nchanterm *n) 363 | { 364 | int w = n->width; 365 | int h = n->height; 366 | if (w != n->cells_width || h != n->cells_height) 367 | { 368 | if (n->cells_front) 369 | free (n->cells_front); 370 | if (n->cells_back) 371 | free (n->cells_back); 372 | n->cells_width = w; 373 | n->cells_height = h; 374 | n->cells_front = calloc (sizeof (NcCell) * n->cells_width * n->cells_height, 1); 375 | n->cells_back = calloc (sizeof (NcCell) * n->cells_width * n->cells_height, 1); 376 | nct_cells_clear (n); 377 | nct_cells_clear_front (n); 378 | } 379 | } 380 | 381 | static NcCell *nct_get_cell (Nchanterm *n, int x, int y) 382 | { 383 | nct_cells_ensure (n); 384 | if (x < 1) x = 1; 385 | if (y < 1) y = 1; 386 | if (x > n->cells_width) x = n->cells_width; 387 | if (y > n->cells_height) y = n->cells_height; 388 | return &n->cells_back[(y-1) * n->cells_width + (x-1)]; 389 | } 390 | 391 | static NcCell *nct_get_front_cell (Nchanterm *n, int x, int y) 392 | { 393 | nct_cells_ensure (n); 394 | if (x < 1) x = 1; 395 | if (y < 1) y = 1; 396 | if (x > n->cells_width) x = n->cells_width; 397 | if (y > n->cells_height) y = n->cells_height; 398 | return &n->cells_front[(y-1) * n->cells_width + (x-1)]; 399 | } 400 | 401 | static void _nct_set_attr (Nchanterm *n, int x, int y, int attr, int color) 402 | { 403 | NcCell *cell = nct_get_cell (n, x, y); 404 | cell->attr = attr; 405 | cell->color = color; 406 | } 407 | 408 | void nct_set (Nchanterm *n, int x, int y, const char *str) 409 | { 410 | int i, bytes = nct_utf8_len (*str); 411 | NcCell *cell = nct_get_cell (n, x, y); 412 | 413 | if (x <= 0 || y <= 0 || 414 | x > n->cells_width || y > n->cells_height) 415 | return; 416 | 417 | for (i = 0; i < bytes; i ++) 418 | cell->str[i] = str[i]; 419 | cell->str[bytes] = 0; 420 | 421 | if (!n->utf8) /* strip down to ascii if utf8 */ 422 | { 423 | if (cell->str[0] & 0x80) 424 | cell->str[0] = '?'; 425 | cell->str[1] = 0; 426 | } 427 | _nct_set_attr (n, x, y, n->mode, n->color); 428 | } 429 | 430 | const char *nct_get (Nchanterm *n, int x, int y) 431 | { 432 | NcCell *cell = nct_get_cell (n, x, y); 433 | return cell->str; 434 | } 435 | 436 | void nct_flush (Nchanterm *n) 437 | { 438 | int x, y; 439 | int w = n->cells_width; 440 | int h = n->cells_height; 441 | static int had_full = -1; 442 | int cx=-1, cy=-1; 443 | nct_cells_ensure (n); 444 | 445 | if (had_full <=0) 446 | { 447 | had_full = 40; // do a full refresh every now and then. 448 | nct_cells_clear_front (n); 449 | } 450 | else 451 | { 452 | had_full--; 453 | } 454 | 455 | for (y = 1; y <= h; y ++) 456 | for (x = 1; x <= w; x ++) 457 | { 458 | 459 | NcCell *back = nct_get_cell (n, x, y); 460 | NcCell *front = nct_get_front_cell (n, x, y); 461 | 462 | /* draw cursor, if any */ 463 | NcCell cursor = {"!", 0, NCT_COLOR_WHITE}; 464 | if (n->mouse_fd != -1) 465 | if (x == (int)n->mouse_x && y == (int)n->mouse_y) 466 | { 467 | strcpy (cursor.str, back->str); 468 | cursor.color = back->color / 8; 469 | cursor.color += back->color % 8; 470 | cursor.attr = back->attr; 471 | back = &cursor; 472 | } 473 | 474 | 475 | if (memcmp (back, front, sizeof (NcCell))) 476 | { 477 | n->color = back->color; 478 | nct_set_attr (n, back->attr); 479 | nct_ensure_mode (n); 480 | 481 | if (y != cy) 482 | printf (ANSI_YX, y, x); 483 | else if (x > cx) 484 | { 485 | if (x - cx == 1) 486 | printf (ANSI_CURSOR_FORWARD1); 487 | else 488 | printf (ANSI_CURSOR_FORWARD, (x - cx)); 489 | } 490 | else 491 | printf (ANSI_YX, y, x); 492 | 493 | printf ("%s", back->str); 494 | cx = x + 1; 495 | cy = y; 496 | memcpy (front, back, sizeof (NcCell)); 497 | } 498 | } 499 | /* reset to defaults, to be in a better terminal state for any 500 | * potential ctrl+c or similar 501 | */ 502 | n->color = NCHANT_DEFAULT_COLORS; 503 | nct_set_attr (n, NCT_A_NORMAL); 504 | nct_ensure_mode (n); 505 | printf (ANSI_YX, n->cursor_y, n->cursor_x); 506 | fflush (NULL); 507 | } 508 | 509 | void nct_reflush (Nchanterm *n) 510 | { 511 | nct_cells_clear_front (n); 512 | nct_flush (n); 513 | } 514 | 515 | Nchanterm *nct_new (void) 516 | { 517 | Nchanterm *term = calloc (sizeof (Nchanterm), 1); 518 | const char *locale = setlocale (LC_ALL, ""); 519 | const char *term_env = getenv ("TERM"); 520 | if (!term_env) 521 | term_env = ""; 522 | if (locale) 523 | { 524 | if (strstr (locale, "utf8") || strstr (locale, "UTF8") || 525 | strstr (locale, "UTF-8") || strstr (locale, "utf-8")) 526 | term->utf8 = 1; 527 | } 528 | else 529 | term->utf8 = 1; /* assume utf8 capable if we do not get a locale */ 530 | 531 | if (strstr (term_env, "Eterm")) 532 | term->utf8 = 0; 533 | 534 | /* some special casing to avoid sending unknown commands to st */ 535 | if (!strcmp (term_env, "st-256color") || !strcmp (term_env, "st")) 536 | term->is_st = 1; 537 | nct_set_size (term, nct_sys_terminal_width (), nct_sys_terminal_height ()); 538 | 539 | if (strstr (term_env, "linux")) 540 | term->mouse_fd = open ("/dev/input/mice", O_RDWR | O_NONBLOCK); 541 | else 542 | term->mouse_fd = -1; 543 | 544 | if (term->mouse_fd != -1) 545 | { 546 | unsigned char reset = 0xff; 547 | write (term->mouse_fd, &reset, 1); /* send a reset */ 548 | } 549 | 550 | printf (XTERM_ALTSCREEN_ON); 551 | return term; 552 | } 553 | 554 | void nct_destroy (Nchanterm *n) 555 | { 556 | if (n->mouse_fd != -1) 557 | close (n->mouse_fd); 558 | free (n); 559 | } 560 | 561 | void nct_set_cursor_pos (Nchanterm *n, int x, int y) 562 | { 563 | n->cursor_x = x; 564 | n->cursor_y = y; 565 | } 566 | 567 | void nct_get_cursor_pos (Nchanterm *n, int *x, int *y) 568 | { 569 | if (x) *x = n->cursor_x; 570 | if (y) *y = n->cursor_y; 571 | } 572 | /*************************** input handling *************************/ 573 | 574 | #include 575 | #include 576 | #include 577 | #include 578 | #include 579 | 580 | #define DELAY_MS 100 581 | 582 | #ifndef MIN 583 | #define MIN(a,b) (((a)<(b))?(a):(b)) 584 | #endif 585 | 586 | static int size_changed = 0; /* XXX: global state */ 587 | static int signal_installed = 0; /* XXX: global state */ 588 | 589 | static const char *mouse_modes[]= 590 | {TERMINAL_MOUSE_OFF, 591 | TERMINAL_MOUSE_ON_BASIC, 592 | TERMINAL_MOUSE_ON_DRAG, 593 | TERMINAL_MOUSE_ON_FULL, 594 | NULL}; 595 | 596 | /* note that a nick can have multiple occurences, the labels 597 | * should be kept the same for all occurences of a combination. */ 598 | typedef struct NcKeyCode { 599 | char *nick; /* programmers name for key (combo) */ 600 | char *label; /* utf8 label for key */ 601 | char sequence[10]; /* terminal sequence */ 602 | } NcKeyCode; 603 | static const NcKeyCode keycodes[]={ 604 | {"up", "↑", "\033[A"}, 605 | {"down", "↓", "\033[B"}, 606 | {"right", "→", "\033[C"}, 607 | {"left", "←", "\033[D"}, 608 | 609 | {"shift-up", "⇧↑", "\033[1;2A"}, 610 | {"shift-down", "⇧↓", "\033[1;2B"}, 611 | {"shift-right", "⇧→", "\033[1;2C"}, 612 | {"shift-left", "⇧←", "\033[1;2D"}, 613 | 614 | {"alt-up", "^↑", "\033[1;3A"}, 615 | {"alt-down", "^↓", "\033[1;3B"}, 616 | {"alt-right", "^→", "\033[1;3C"}, 617 | {"alt-left", "^←", "\033[1;3D"}, 618 | 619 | {"alt-shift-up", "alt-s↑", "\033[1;4A"}, 620 | {"alt-shift-down", "alt-s↓", "\033[1;4B"}, 621 | {"alt-shift-right", "alt-s→", "\033[1;4C"}, 622 | {"alt-shift-left", "alt-s←", "\033[1;4D"}, 623 | 624 | {"control-up", "^↑", "\033[1;5A"}, 625 | {"control-down", "^↓", "\033[1;5B"}, 626 | {"control-right", "^→", "\033[1;5C"}, 627 | {"control-left", "^←", "\033[1;5D"}, 628 | 629 | /* putty */ 630 | {"control-up", "^↑", "\033OA"}, 631 | {"control-down", "^↓", "\033OB"}, 632 | {"control-right", "^→", "\033OC"}, 633 | {"control-left", "^←", "\033OD"}, 634 | 635 | {"control-shift-up", "^⇧↑", "\033[1;6A"}, 636 | {"control-shift-down", "^⇧↓", "\033[1;6B"}, 637 | {"control-shift-right", "^⇧→", "\033[1;6C"}, 638 | {"control-shift-left", "^⇧←", "\033[1;6D"}, 639 | 640 | {"control-up", "^↑", "\033Oa"}, 641 | {"control-down", "^↓", "\033Ob"}, 642 | {"control-right", "^→", "\033Oc"}, 643 | {"control-left", "^←", "\033Od"}, 644 | 645 | {"shift-up", "⇧↑", "\033[a"}, 646 | {"shift-down", "⇧↓", "\033[b"}, 647 | {"shift-right", "⇧→", "\033[c"}, 648 | {"shift-left", "⇧←", "\033[d"}, 649 | 650 | {"insert", "ins", "\033[2~"}, 651 | {"delete", "del", "\033[3~"}, 652 | {"page-up", "PgUp", "\033[5~"}, 653 | {"page-down", "PdDn", "\033[6~"}, 654 | {"home", "Home", "\033OH"}, 655 | {"end", "End", "\033OF"}, 656 | {"home", "Home", "\033[H"}, 657 | {"end", "End", "\033[F"}, 658 | {"control-delete", "^del", "\033[3;5~"}, 659 | {"shift-delete", "⇧del", "\033[3;2~"}, 660 | {"control-shift-delete","^⇧del", "\033[3;6~"}, 661 | 662 | {"F1", "F1", "\033[11~"}, 663 | {"F2", "F2", "\033[12~"}, 664 | {"F3", "F3", "\033[13~"}, 665 | {"F4", "F4", "\033[14~"}, 666 | {"F1", "F1", "\033OP"}, 667 | {"F2", "F2", "\033OQ"}, 668 | {"F3", "F3", "\033OR"}, 669 | {"F4", "F4", "\033OS"}, 670 | {"F5", "F5", "\033[15~"}, 671 | {"F6", "F6", "\033[16~"}, 672 | {"F7", "F7", "\033[17~"}, 673 | {"F8", "F8", "\033[18~"}, 674 | {"F9", "F9", "\033[19~"}, 675 | {"F9", "F9", "\033[20~"}, 676 | {"F10", "F10", "\033[21~"}, 677 | {"F11", "F11", "\033[22~"}, 678 | {"F12", "F12", "\033[23~"}, 679 | {"tab", "↹", {9, '\0'}}, 680 | {"shift-tab", "shift+↹", "\033[Z"}, 681 | {"backspace", "⌫", {127, '\0'}}, 682 | {"space", "␣", " "}, 683 | {"esc", "␛", "\033"}, 684 | {"return", "⏎", {10,0}}, 685 | {"return", "⏎", {13,0}}, 686 | /* this section could be autogenerated by code */ 687 | {"control-a", "^A", {1,0}}, 688 | {"control-b", "^B", {2,0}}, 689 | {"control-c", "^C", {3,0}}, 690 | {"control-d", "^D", {4,0}}, 691 | {"control-e", "^E", {5,0}}, 692 | {"control-f", "^F", {6,0}}, 693 | {"control-g", "^G", {7,0}}, 694 | {"control-h", "^H", {8,0}}, /* backspace? */ 695 | {"control-i", "^I", {9,0}}, 696 | {"control-j", "^J", {10,0}}, 697 | {"control-k", "^K", {11,0}}, 698 | {"control-l", "^L", {12,0}}, 699 | {"control-n", "^N", {14,0}}, 700 | {"control-o", "^O", {15,0}}, 701 | {"control-p", "^P", {16,0}}, 702 | {"control-q", "^Q", {17,0}}, 703 | {"control-r", "^R", {18,0}}, 704 | {"control-s", "^S", {19,0}}, 705 | {"control-t", "^T", {20,0}}, 706 | {"control-u", "^U", {21,0}}, 707 | {"control-v", "^V", {22,0}}, 708 | {"control-w", "^W", {23,0}}, 709 | {"control-x", "^X", {24,0}}, 710 | {"control-y", "^Y", {25,0}}, 711 | {"control-z", "^Z", {26,0}}, 712 | {"alt-0", "%0", "\0330"}, 713 | {"alt-1", "%1", "\0331"}, 714 | {"alt-2", "%2", "\0332"}, 715 | {"alt-3", "%3", "\0333"}, 716 | {"alt-4", "%4", "\0334"}, 717 | {"alt-5", "%5", "\0335"}, 718 | {"alt-6", "%6", "\0336"}, 719 | {"alt-7", "%7", "\0337"}, /* backspace? */ 720 | {"alt-8", "%8", "\0338"}, 721 | {"alt-9", "%9", "\0339"}, 722 | {"alt-+", "%+", "\033+"}, 723 | {"alt--", "%-", "\033-"}, 724 | {"alt-/", "%/", "\033/"}, 725 | {"alt-a", "%A", "\033a"}, 726 | {"alt-b", "%B", "\033b"}, 727 | {"alt-c", "%C", "\033c"}, 728 | {"alt-d", "%D", "\033d"}, 729 | {"alt-e", "%E", "\033e"}, 730 | {"alt-f", "%F", "\033f"}, 731 | {"alt-g", "%G", "\033g"}, 732 | {"alt-h", "%H", "\033h"}, /* backspace? */ 733 | {"alt-i", "%I", "\033i"}, 734 | {"alt-j", "%J", "\033j"}, 735 | {"alt-k", "%K", "\033k"}, 736 | {"alt-l", "%L", "\033l"}, 737 | {"alt-n", "%N", "\033m"}, 738 | {"alt-n", "%N", "\033n"}, 739 | {"alt-o", "%O", "\033o"}, 740 | {"alt-p", "%P", "\033p"}, 741 | {"alt-q", "%Q", "\033q"}, 742 | {"alt-r", "%R", "\033r"}, 743 | {"alt-s", "%S", "\033s"}, 744 | {"alt-t", "%T", "\033t"}, 745 | {"alt-u", "%U", "\033u"}, 746 | {"alt-v", "%V", "\033v"}, 747 | {"alt-w", "%W", "\033w"}, 748 | {"alt-x", "%X", "\033x"}, 749 | {"alt-y", "%Y", "\033y"}, 750 | {"alt-z", "%Z", "\033z"}, 751 | /* Linux Console */ 752 | {"home", "Home", "\033[1~"}, 753 | {"end", "End", "\033[4~"}, 754 | {"F1", "F1", "\033[[A"}, 755 | {"F2", "F2", "\033[[B"}, 756 | {"F3", "F3", "\033[[C"}, 757 | {"F4", "F4", "\033[[D"}, 758 | {"F5", "F5", "\033[[E"}, 759 | {"F6", "F6", "\033[[F"}, 760 | {"F7", "F7", "\033[[G"}, 761 | {"F8", "F8", "\033[[H"}, 762 | {"F9", "F9", "\033[[I"}, 763 | {"F10", "F10", "\033[[J"}, 764 | {"F11", "F11", "\033[[K"}, 765 | {"F12", "F12", "\033[[L"}, 766 | {NULL, } 767 | }; 768 | 769 | static struct termios orig_attr; /* in order to restore at exit */ 770 | static int nc_is_raw = 0; 771 | static int atexit_registered = 0; 772 | static int mouse_mode = NC_MOUSE_NONE; 773 | 774 | static void _nc_noraw (void) 775 | { 776 | if (nc_is_raw && tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_attr) != -1) 777 | nc_is_raw = 0; 778 | } 779 | 780 | static void 781 | nc_at_exit (void) 782 | { 783 | printf (TERMINAL_MOUSE_OFF); 784 | _nc_noraw(); 785 | if (mouse_mode) 786 | printf (TERMINAL_MOUSE_OFF); 787 | printf (XTERM_ALTSCREEN_OFF); 788 | } 789 | 790 | static int _nc_raw (void) 791 | { 792 | struct termios raw; 793 | if (!isatty (STDIN_FILENO)) 794 | return -1; 795 | if (!atexit_registered) 796 | { 797 | atexit (nc_at_exit); 798 | atexit_registered = 1; 799 | } 800 | if (tcgetattr (STDIN_FILENO, &orig_attr) == -1) 801 | return -1; 802 | raw = orig_attr; /* modify the original mode */ 803 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 804 | raw.c_oflag &= ~(OPOST); 805 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 806 | raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ 807 | if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw) < 0) 808 | return -1; 809 | nc_is_raw = 1; 810 | tcdrain(STDIN_FILENO); 811 | tcflush(STDIN_FILENO, 1); 812 | return 0; 813 | } 814 | 815 | static int match_keycode (const char *buf, int length, const NcKeyCode **ret) 816 | { 817 | int i; 818 | int matches = 0; 819 | 820 | if (!strncmp (buf, "\033[M", MIN(length,3))) 821 | { 822 | if (length >= 6) 823 | return 9001; 824 | return 2342; 825 | } 826 | for (i = 0; keycodes[i].nick; i++) 827 | if (!strncmp (buf, keycodes[i].sequence, length)) 828 | { 829 | matches ++; 830 | if ((int)strlen (keycodes[i].sequence) == length && ret) 831 | { 832 | *ret = &keycodes[i]; 833 | return 1; 834 | } 835 | } 836 | if (matches != 1 && ret) 837 | *ret = NULL; 838 | return matches==1?2:matches; 839 | } 840 | 841 | static void nc_resize_term (int UNUSED dummy) 842 | { 843 | size_changed = 1; 844 | } 845 | 846 | int nct_has_event (Nchanterm UNUSED *n, int delay_ms) 847 | { 848 | struct timeval tv; 849 | int retval; 850 | fd_set rfds; 851 | 852 | if (size_changed) 853 | return 1; 854 | FD_ZERO (&rfds); 855 | FD_SET (STDIN_FILENO, &rfds); 856 | tv.tv_sec = 0; tv.tv_usec = delay_ms * 1000; 857 | retval = select (1, &rfds, NULL, NULL, &tv); 858 | if (size_changed) 859 | return 1; 860 | return retval == 1 && retval != -1; 861 | } 862 | 863 | 864 | static const char *mouse_get_event_int (Nchanterm *n, int *x, int *y) 865 | { 866 | static int prev_state = 0; 867 | const char *ret = "mouse-motion"; 868 | float relx, rely; 869 | signed char buf[3]; 870 | read (n->mouse_fd, buf, 3); 871 | relx = buf[1]; 872 | rely = -buf[2]; 873 | 874 | n->mouse_x += relx * 0.1; 875 | n->mouse_y += rely * 0.1; 876 | 877 | if (n->mouse_x < 1) n->mouse_x = 1; 878 | if (n->mouse_y < 1) n->mouse_y = 1; 879 | if (n->mouse_x >= n->width) n->mouse_x = n->width; 880 | if (n->mouse_y >= n->height) n->mouse_y = n->height; 881 | 882 | if (x) *x = n->mouse_x; 883 | if (y) *y = n->mouse_y; 884 | 885 | if ((prev_state & 1) != (buf[0] & 1)) 886 | { 887 | if (buf[0] & 1) ret = "mouse-press"; 888 | } 889 | else if (buf[0] & 1) 890 | ret = "mouse-drag"; 891 | 892 | if ((prev_state & 2) != (buf[0] & 2)) 893 | { 894 | if (buf[0] & 2) ret = "mouse2-press"; 895 | } 896 | else if (buf[0] & 2) 897 | ret = "mouse2-drag"; 898 | 899 | if ((prev_state & 4) != (buf[0] & 4)) 900 | { 901 | if (buf[0] & 4) ret = "mouse1-press"; 902 | } 903 | else if (buf[0] & 4) 904 | ret = "mouse1-drag"; 905 | 906 | prev_state = buf[0]; 907 | return ret; 908 | } 909 | 910 | static const char *mev_type = NULL; 911 | static int mev_x = 0; 912 | static int mev_y = 0; 913 | static int mev_q = 0; 914 | 915 | static const char *mouse_get_event (Nchanterm UNUSED *n, int *x, int *y) 916 | { 917 | if (!mev_q) 918 | return NULL; 919 | *x = mev_x; 920 | *y = mev_y; 921 | mev_q = 0; 922 | return mev_type; 923 | } 924 | 925 | static int mouse_has_event (Nchanterm *n) 926 | { 927 | struct timeval tv; 928 | int retval; 929 | 930 | if (mouse_mode == NC_MOUSE_NONE) 931 | return 0; 932 | 933 | if (mev_q) 934 | return 1; 935 | 936 | if (n->mouse_fd == -1) 937 | return 0; 938 | 939 | { 940 | fd_set rfds; 941 | FD_ZERO (&rfds); 942 | FD_SET(n->mouse_fd, &rfds); 943 | tv.tv_sec = 0; tv.tv_usec = 0; 944 | retval = select (n->mouse_fd+1, &rfds, NULL, NULL, &tv); 945 | } 946 | 947 | if (retval != 0) 948 | { 949 | int nx = 0, ny = 0; 950 | const char *type = mouse_get_event_int (n, &nx, &ny); 951 | 952 | if ((mouse_mode < NC_MOUSE_DRAG && mev_type && !strcmp (mev_type, "drag")) || 953 | (mouse_mode < NC_MOUSE_ALL && mev_type && !strcmp (mev_type, "motion"))) 954 | { 955 | mev_q = 0; 956 | return mouse_has_event (n); 957 | } 958 | 959 | if ((mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse-motion")) || 960 | (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse1-drag")) || 961 | (mev_type && !strcmp (type, mev_type) && !strcmp (type, "mouse2-drag"))) 962 | { 963 | if (nx == mev_x && ny == mev_y) 964 | { 965 | mev_q = 0; 966 | return mouse_has_event (n); 967 | } 968 | } 969 | mev_x = nx; 970 | mev_y = ny; 971 | mev_type = type; 972 | mev_q = 1; 973 | } 974 | return retval != 0; 975 | } 976 | 977 | const char *nct_get_event (Nchanterm *n, int timeoutms, int *x, int *y) 978 | { 979 | unsigned char buf[20]; 980 | int length; 981 | 982 | 983 | if (x) *x = -1; 984 | if (y) *y = -1; 985 | 986 | if (!signal_installed) 987 | { 988 | _nc_raw (); 989 | signal_installed = 1; 990 | signal (SIGWINCH, nc_resize_term); 991 | } 992 | if (mouse_mode) 993 | printf(mouse_modes[mouse_mode]); 994 | 995 | { 996 | int elapsed = 0; 997 | int got_event = 0; 998 | 999 | do { 1000 | if (size_changed) 1001 | { 1002 | size_changed = 0; 1003 | return "size-changed"; 1004 | } 1005 | got_event = mouse_has_event (n); 1006 | if (!got_event) 1007 | got_event = nct_has_event (n, MIN(DELAY_MS, timeoutms-elapsed)); 1008 | if (size_changed) 1009 | { 1010 | size_changed = 0; 1011 | return "size-changed"; 1012 | } 1013 | /* only do this if the client has asked for idle events, 1014 | * and perhaps programmed the ms timer? 1015 | */ 1016 | elapsed += MIN(DELAY_MS, timeoutms-elapsed); 1017 | if (!got_event && timeoutms && elapsed >= timeoutms) 1018 | return "idle"; 1019 | } while (!got_event); 1020 | } 1021 | 1022 | if (mouse_has_event (n)) 1023 | return mouse_get_event (n, x, y); 1024 | 1025 | for (length = 0; length < 10; length ++) 1026 | if (read (STDIN_FILENO, &buf[length], 1) != -1) 1027 | { 1028 | const NcKeyCode *match = NULL; 1029 | 1030 | /* special case ESC, so that we can use it alone in keybindings */ 1031 | if (length == 0 && buf[0] == 27) 1032 | { 1033 | struct timeval tv; 1034 | fd_set rfds; 1035 | FD_ZERO (&rfds); 1036 | FD_SET (STDIN_FILENO, &rfds); 1037 | tv.tv_sec = 0; 1038 | tv.tv_usec = 1000 * DELAY_MS; 1039 | if (select (1, &rfds, NULL, NULL, &tv) == 0) 1040 | return "esc"; 1041 | } 1042 | 1043 | switch (match_keycode ((void*)buf, length + 1, &match)) 1044 | { 1045 | case 1: /* unique match */ 1046 | if (!match) 1047 | return NULL; 1048 | return match->nick; 1049 | break; 1050 | case 9001: /* mouse event */ 1051 | if (x) *x = ((unsigned char)buf[4]-32)*1.0; 1052 | if (y) *y = ((unsigned char)buf[5]-32)*1.0; 1053 | switch (buf[3]) 1054 | { 1055 | case 32: return "mouse-press"; 1056 | case 33: return "mouse1-press"; 1057 | case 34: return "mouse2-press"; 1058 | case 40: return "alt-mouse-press"; 1059 | case 41: return "alt-mouse1-press"; 1060 | case 42: return "alt-mouse2-press"; 1061 | case 48: return "control-mouse-press"; 1062 | case 49: return "control-mouse1-press"; 1063 | case 50: return "control-mouse2-press"; 1064 | case 56: return "alt-control-mouse-press"; 1065 | case 57: return "alt-control-mouse1-press"; 1066 | case 58: return "alt-control-mouse2-press"; 1067 | case 64: return "mouse-drag"; 1068 | case 65: return "mouse1-drag"; 1069 | case 66: return "mouse2-drag"; 1070 | case 71: return "mouse-motion"; /* shift+motion */ 1071 | case 72: return "alt-mouse-drag"; 1072 | case 73: return "alt-mouse1-drag"; 1073 | case 74: return "alt-mouse2-drag"; 1074 | case 75: return "mouse-motion"; /* alt+motion */ 1075 | case 80: return "control-mouse-drag"; 1076 | case 81: return "control-mouse1-drag"; 1077 | case 82: return "control-mouse2-drag"; 1078 | case 83: return "mouse-motion"; /* ctrl+motion */ 1079 | case 91: return "mouse-motion"; /* ctrl+alt+motion */ 1080 | case 95: return "mouse-motion"; /* ctrl+alt+shift+motion */ 1081 | case 96: return "scroll-up"; 1082 | case 97: return "scroll-down"; 1083 | case 100: return "shift-scroll-up"; 1084 | case 101: return "shift-scroll-down"; 1085 | case 104: return "alt-scroll-up"; 1086 | case 105: return "alt-scroll-down"; 1087 | case 112: return "control-scroll-up"; 1088 | case 113: return "control-scroll-down"; 1089 | case 116: return "control-shift-scroll-up"; 1090 | case 117: return "control-shift-scroll-down"; 1091 | case 35: /* (or release) */ 1092 | case 51: /* (or ctrl-release) */ 1093 | case 43: /* (or alt-release) */ 1094 | case 67: return "mouse-motion"; 1095 | /* have a separate mouse-drag ? */ 1096 | default: { 1097 | static char rbuf[100]; 1098 | sprintf (rbuf, "mouse (unhandled state: %i)", buf[3]); 1099 | return rbuf; 1100 | } 1101 | } 1102 | case 0: /* no matches, bail*/ 1103 | { 1104 | static char ret[256]; 1105 | if (length == 0 && nct_utf8_len (buf[0])>1) /* single unicode 1106 | char */ 1107 | { 1108 | read (STDIN_FILENO, &buf[length+1], nct_utf8_len(buf[0])-1); 1109 | buf[nct_utf8_len(buf[0])]=0; 1110 | strcpy (ret, (void*)buf); 1111 | return ret; 1112 | } 1113 | if (length == 0) /* ascii */ 1114 | { 1115 | buf[1]=0; 1116 | strcpy (ret, (void*)buf); 1117 | return ret; 1118 | } 1119 | sprintf (ret, "unhandled %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c' %i:'%c'", 1120 | length>=0? buf[0]: 0, length>=0? buf[0]>31?buf[0]:'?': ' ', 1121 | length>=1? buf[1]: 0, length>=1? buf[1]>31?buf[1]:'?': ' ', 1122 | length>=2? buf[2]: 0, length>=2? buf[2]>31?buf[2]:'?': ' ', 1123 | length>=3? buf[3]: 0, length>=3? buf[3]>31?buf[3]:'?': ' ', 1124 | length>=4? buf[4]: 0, length>=4? buf[4]>31?buf[4]:'?': ' ', 1125 | length>=5? buf[5]: 0, length>=5? buf[5]>31?buf[5]:'?': ' ', 1126 | length>=6? buf[6]: 0, length>=6? buf[6]>31?buf[6]:'?': ' '); 1127 | return ret; 1128 | } 1129 | return NULL; 1130 | default: /* continue */ 1131 | break; 1132 | } 1133 | } 1134 | else 1135 | return "key read eek"; 1136 | return "fail"; 1137 | } 1138 | 1139 | const char *nct_key_get_label (Nchanterm UNUSED *n, const char *nick) 1140 | { 1141 | int j; 1142 | int found = -1; 1143 | for (j = 0; keycodes[j].nick; j++) 1144 | if (found == -1 && !strcmp (keycodes[j].nick, nick)) 1145 | return keycodes[j].label; 1146 | return NULL; 1147 | } 1148 | 1149 | void nct_mouse (Nchanterm *term, int mode) 1150 | { 1151 | if (term->is_st && mode > 1) 1152 | mode = 1; 1153 | if (mode != mouse_mode) 1154 | { 1155 | printf (mouse_modes[mode]); 1156 | fflush (stdout); 1157 | } 1158 | mouse_mode = mode; 1159 | } 1160 | #endif 1161 | -------------------------------------------------------------------------------- /nchanterm.h: -------------------------------------------------------------------------------- 1 | /* see the start of nchanterm.c for the function declarations and API docs */ 2 | 3 | #ifndef NCHANTERM_H 4 | 5 | /* nchanterm is a "single file C library" meaning that it can be dropped into 6 | * a project and directly #included from a .c file (if there is only one 7 | * compilation unit using it). By setting this #define we make the included 8 | * .c file strip out the code leaving only the function declarations. 9 | */ 10 | #define NCHANTERM_HEADER_ONLY 11 | #include "nchanterm.c" 12 | #endif 13 | -------------------------------------------------------------------------------- /nchanterm.pc: -------------------------------------------------------------------------------- 1 | prefix=/usr/local 2 | exec_prefix=${prefix} 3 | libdir=${exec_prefix}/lib 4 | includedir=${prefix}/include 5 | apiversion=0.0 6 | 7 | Name: nchanterm 8 | Description: utf8 vt102 terminal abstraction 9 | Version: 0.0.0 10 | 11 | Libs: -L${libdir} -lnchanterm 12 | Cflags: -I${includedir}/nchanterm 13 | --------------------------------------------------------------------------------