1/* $NetBSD: color.c,v 1.47 2022/10/19 06:09:27 blymn Exp $ */ 2 3/* 4 * Copyright (c) 2000 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Julian Coleman. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33#ifndef lint 34__RCSID("$NetBSD: color.c,v 1.47 2022/10/19 06:09:27 blymn Exp $"); 35#endif /* not lint */ 36 37#include "curses.h" 38#include "curses_private.h" 39 40/* Have we initialised colours? */ 41int __using_color = 0; 42int __do_color_init = 0; /* force refresh to init color in all cells */ 43 44/* Default colour number */ 45attr_t __default_color = 0; 46 47/* Default colour pair values - white on black. */ 48struct __pair __default_pair = {COLOR_WHITE, COLOR_BLACK, 0}; 49 50/* Default colour values */ 51/* Flags for colours and pairs */ 52#define __USED 0x01 53 54static void 55__change_pair(short); 56 57static int 58init_color_value(short, short, short, short); 59 60/* 61 * has_colors -- 62 * Check if terminal has colours. 63 */ 64bool 65has_colors(void) 66{ 67 if (max_colors > 0 && max_pairs > 0 && 68 ((set_a_foreground != NULL && set_a_background != NULL) || 69 initialize_pair != NULL || initialize_color != NULL || 70 (set_background != NULL && set_foreground != NULL))) 71 return true; 72 else 73 return false; 74} 75 76/* 77 * can_change_color -- 78 * Check if terminal can change colours. 79 */ 80bool 81can_change_color(void) 82{ 83 return can_change ? true : false; 84} 85 86/* 87 * start_color -- 88 * Initialise colour support. 89 */ 90int 91start_color(void) 92{ 93 int i; 94 attr_t temp_nc; 95 struct __winlist *wlp; 96 WINDOW *win; 97 int y, x; 98 99 if (has_colors() == FALSE) 100 return ERR; 101 102 /* Max colours and colour pairs */ 103 if (max_colors == -1) 104 COLORS = 0; 105 else { 106 COLORS = max_colors > MAX_COLORS ? MAX_COLORS : max_colors; 107 if (max_pairs == -1) { 108 COLOR_PAIRS = 0; 109 COLORS = 0; 110 } else { 111 COLOR_PAIRS = (max_pairs > MAX_PAIRS - 1 ? 112 MAX_PAIRS - 1 : max_pairs); 113 /* Use the last colour pair for curses default. */ 114#ifdef __OLD_DEFAULT_COLOR 115 __default_color = COLOR_PAIR(MAX_PAIRS - 1); 116#else 117 __default_color = COLOR_PAIR(0); 118#endif 119 } 120 } 121 if (!COLORS) 122 return ERR; 123 124 _cursesi_screen->COLORS = COLORS; 125 _cursesi_screen->COLOR_PAIRS = COLOR_PAIRS; 126 127 /* Reset terminal colour and colour pairs. */ 128 if (orig_colors != NULL) 129 tputs(orig_colors, 0, __cputchar); 130 if (orig_pair != NULL) { 131 tputs(orig_pair, 0, __cputchar); 132 curscr->wattr &= _cursesi_screen->mask_op; 133 } 134 135 /* Type of colour manipulation - ANSI/TEK/HP/other */ 136 if (set_a_foreground != NULL && set_a_background != NULL) 137 _cursesi_screen->color_type = COLOR_ANSI; 138 else if (initialize_pair != NULL) 139 _cursesi_screen->color_type = COLOR_HP; 140 else if (initialize_color != NULL) 141 _cursesi_screen->color_type = COLOR_TEK; 142 else if (set_foreground != NULL && set_background != NULL) 143 _cursesi_screen->color_type = COLOR_OTHER; 144 else 145 return(ERR); /* Unsupported colour method */ 146 147#ifdef DEBUG 148 __CTRACE(__CTRACE_COLOR, "start_color: COLORS = %d, COLOR_PAIRS = %d", 149 COLORS, COLOR_PAIRS); 150 switch (_cursesi_screen->color_type) { 151 case COLOR_ANSI: 152 __CTRACE(__CTRACE_COLOR, " (ANSI style)\n"); 153 break; 154 case COLOR_HP: 155 __CTRACE(__CTRACE_COLOR, " (HP style)\n"); 156 break; 157 case COLOR_TEK: 158 __CTRACE(__CTRACE_COLOR, " (Tektronics style)\n"); 159 break; 160 case COLOR_OTHER: 161 __CTRACE(__CTRACE_COLOR, " (Other style)\n"); 162 break; 163 } 164#endif 165 166 /* 167 * Attributes that cannot be used with color. 168 * Store these in an attr_t for wattrset()/wattron(). 169 */ 170 _cursesi_screen->nca = __NORMAL; 171 if (no_color_video != -1) { 172 temp_nc = (attr_t)t_no_color_video(_cursesi_screen->term); 173 if (temp_nc & 0x0001) 174 _cursesi_screen->nca |= __STANDOUT; 175 if (temp_nc & 0x0002) 176 _cursesi_screen->nca |= __UNDERSCORE; 177 if (temp_nc & 0x0004) 178 _cursesi_screen->nca |= __REVERSE; 179 if (temp_nc & 0x0008) 180 _cursesi_screen->nca |= __BLINK; 181 if (temp_nc & 0x0010) 182 _cursesi_screen->nca |= __DIM; 183 if (temp_nc & 0x0020) 184 _cursesi_screen->nca |= __BOLD; 185 if (temp_nc & 0x0040) 186 _cursesi_screen->nca |= __BLANK; 187 if (temp_nc & 0x0080) 188 _cursesi_screen->nca |= __PROTECT; 189 if (temp_nc & 0x0100) 190 _cursesi_screen->nca |= __ALTCHARSET; 191 } 192 __CTRACE(__CTRACE_COLOR, "start_color: _cursesi_screen->nca = %08x\n", 193 _cursesi_screen->nca); 194 195 /* Set up initial 8 colours */ 196#define RGB_ON 680 /* Allow for bright colours */ 197 if (COLORS >= COLOR_BLACK) 198 (void)init_color_value(COLOR_BLACK, 0, 0, 0); 199 if (COLORS >= COLOR_RED) 200 (void)init_color_value(COLOR_RED, RGB_ON, 0, 0); 201 if (COLORS >= COLOR_GREEN) 202 (void)init_color_value(COLOR_GREEN, 0, RGB_ON, 0); 203 if (COLORS >= COLOR_YELLOW) 204 (void)init_color_value(COLOR_YELLOW, RGB_ON, RGB_ON, 0); 205 if (COLORS >= COLOR_BLUE) 206 (void)init_color_value(COLOR_BLUE, 0, 0, RGB_ON); 207 if (COLORS >= COLOR_MAGENTA) 208 (void)init_color_value(COLOR_MAGENTA, RGB_ON, 0, RGB_ON); 209 if (COLORS >= COLOR_CYAN) 210 (void)init_color_value(COLOR_CYAN, 0, RGB_ON, RGB_ON); 211 if (COLORS >= COLOR_WHITE) 212 (void)init_color_value(COLOR_WHITE, RGB_ON, RGB_ON, RGB_ON); 213 214 /* Initialise other colours */ 215 for (i = 8; i < COLORS; i++) { 216 _cursesi_screen->colours[i].red = 0; 217 _cursesi_screen->colours[i].green = 0; 218 _cursesi_screen->colours[i].blue = 0; 219 _cursesi_screen->colours[i].flags = 0; 220 } 221 222 /* Initialise pair 0 to default colours. */ 223 _cursesi_screen->colour_pairs[0].fore = -1; 224 _cursesi_screen->colour_pairs[0].back = -1; 225 _cursesi_screen->colour_pairs[0].flags = 0; 226 227 /* Initialise user colour pairs to default (white on black) */ 228 for (i = 0; i < COLOR_PAIRS; i++) { 229 _cursesi_screen->colour_pairs[i].fore = COLOR_WHITE; 230 _cursesi_screen->colour_pairs[i].back = COLOR_BLACK; 231 _cursesi_screen->colour_pairs[i].flags = 0; 232 } 233 234 /* Initialise default colour pair. */ 235 _cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].fore = 236 __default_pair.fore; 237 _cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].back = 238 __default_pair.back; 239 _cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].flags = 240 __default_pair.flags; 241 242 __using_color = 1; 243 __do_color_init = 1; 244 245 /* Set all positions on all windows to curses default colours. */ 246 for (wlp = _cursesi_screen->winlistp; wlp != NULL; wlp = wlp->nextp) { 247 win = wlp->winp; 248 if (wlp->winp != __virtscr && wlp->winp != curscr) { 249 /* Set color attribute on other windows */ 250 win->battr |= __default_color; 251 for (y = 0; y < win->maxy; y++) { 252 win->alines[y]->flags |= __ISFORCED; 253 for (x = 0; x < win->maxx; x++) { 254 win->alines[y]->line[x].attr &= ~__COLOR; 255 win->alines[y]->line[x].attr |= __default_color; 256 } 257 } 258 __touchwin(win, 0); 259 } 260 } 261 262 return(OK); 263} 264 265/* 266 * init_pair -- 267 * Set pair foreground and background colors. 268 * Our default colour ordering is ANSI - 1 = red, 4 = blue, 3 = yellow, 269 * 6 = cyan. The older style (Sb/Sf) uses 1 = blue, 4 = red, 3 = cyan, 270 * 6 = yellow, so we swap them here and in pair_content(). 271 */ 272int 273init_pair(short pair, short fore, short back) 274{ 275 int changed; 276 277 __CTRACE(__CTRACE_COLOR, "init_pair: %d, %d, %d\n", pair, fore, back); 278 279 if (pair < 0 || pair >= COLOR_PAIRS) 280 return ERR; 281 282 if (pair == 0) /* Ignore request for pair 0, it is default. */ 283 return OK; 284 285 if (fore >= COLORS) 286 return ERR; 287 if (back >= COLORS) 288 return ERR; 289 290 /* Swap red/blue and yellow/cyan */ 291 if (_cursesi_screen->color_type == COLOR_OTHER) { 292 switch (fore) { 293 case COLOR_RED: 294 fore = COLOR_BLUE; 295 break; 296 case COLOR_BLUE: 297 fore = COLOR_RED; 298 break; 299 case COLOR_YELLOW: 300 fore = COLOR_CYAN; 301 break; 302 case COLOR_CYAN: 303 fore = COLOR_YELLOW; 304 break; 305 } 306 switch (back) { 307 case COLOR_RED: 308 back = COLOR_BLUE; 309 break; 310 case COLOR_BLUE: 311 back = COLOR_RED; 312 break; 313 case COLOR_YELLOW: 314 back = COLOR_CYAN; 315 break; 316 case COLOR_CYAN: 317 back = COLOR_YELLOW; 318 break; 319 } 320 } 321 322 if ((_cursesi_screen->colour_pairs[pair].flags & __USED) && 323 (fore != _cursesi_screen->colour_pairs[pair].fore || 324 back != _cursesi_screen->colour_pairs[pair].back)) 325 changed = 1; 326 else 327 changed = 0; 328 329 _cursesi_screen->colour_pairs[pair].flags |= __USED; 330 _cursesi_screen->colour_pairs[pair].fore = fore; 331 _cursesi_screen->colour_pairs[pair].back = back; 332 333 /* XXX: need to initialise HP style (Ip) */ 334 335 if (changed) 336 __change_pair(pair); 337 return OK; 338} 339 340/* 341 * pair_content -- 342 * Get pair foreground and background colours. 343 */ 344int 345pair_content(short pair, short *forep, short *backp) 346{ 347 if (pair < 0 || pair > _cursesi_screen->COLOR_PAIRS) 348 return ERR; 349 350 *forep = _cursesi_screen->colour_pairs[pair].fore; 351 *backp = _cursesi_screen->colour_pairs[pair].back; 352 353 /* Swap red/blue and yellow/cyan */ 354 if (_cursesi_screen->color_type == COLOR_OTHER) { 355 switch (*forep) { 356 case COLOR_RED: 357 *forep = COLOR_BLUE; 358 break; 359 case COLOR_BLUE: 360 *forep = COLOR_RED; 361 break; 362 case COLOR_YELLOW: 363 *forep = COLOR_CYAN; 364 break; 365 case COLOR_CYAN: 366 *forep = COLOR_YELLOW; 367 break; 368 } 369 switch (*backp) { 370 case COLOR_RED: 371 *backp = COLOR_BLUE; 372 break; 373 case COLOR_BLUE: 374 *backp = COLOR_RED; 375 break; 376 case COLOR_YELLOW: 377 *backp = COLOR_CYAN; 378 break; 379 case COLOR_CYAN: 380 *backp = COLOR_YELLOW; 381 break; 382 } 383 } 384 return OK; 385} 386 387/* 388 * init_color_Value -- 389 * Set colour red, green and blue values. 390 */ 391static int 392init_color_value(short color, short red, short green, short blue) 393{ 394 if (color < 0 || color >= _cursesi_screen->COLORS) 395 return ERR; 396 397 _cursesi_screen->colours[color].red = red; 398 _cursesi_screen->colours[color].green = green; 399 _cursesi_screen->colours[color].blue = blue; 400 return OK; 401} 402 403/* 404 * init_color -- 405 * Set colour red, green and blue values. 406 * Change color on screen. 407 */ 408int 409init_color(short color, short red, short green, short blue) 410{ 411 __CTRACE(__CTRACE_COLOR, "init_color: %d, %d, %d, %d\n", 412 color, red, green, blue); 413 if (init_color_value(color, red, green, blue) == ERR) 414 return ERR; 415 if (!can_change || t_initialize_color(_cursesi_screen->term) == NULL) 416 return ERR; 417 tputs(tiparm(t_initialize_color(_cursesi_screen->term), 418 color, red, green, blue), 0, __cputchar); 419 return OK; 420} 421 422/* 423 * color_content -- 424 * Get colour red, green and blue values. 425 */ 426int 427color_content(short color, short *redp, short *greenp, short *bluep) 428{ 429 if (color < 0 || color >= _cursesi_screen->COLORS) 430 return ERR; 431 432 *redp = _cursesi_screen->colours[color].red; 433 *greenp = _cursesi_screen->colours[color].green; 434 *bluep = _cursesi_screen->colours[color].blue; 435 return OK; 436} 437 438/* 439 * use_default_colors -- 440 * Use terminal default colours instead of curses default colour. 441 */ 442int 443use_default_colors(void) 444{ 445 __CTRACE(__CTRACE_COLOR, "use_default_colors\n"); 446 447 return (assume_default_colors(-1, -1)); 448} 449 450/* 451 * assume_default_colors -- 452 * Set the default foreground and background colours. 453 */ 454int 455assume_default_colors(short fore, short back) 456{ 457 __CTRACE(__CTRACE_COLOR, "assume_default_colors: %d, %d\n", 458 fore, back); 459 __CTRACE(__CTRACE_COLOR, 460 "assume_default_colors: default_colour = %d, pair_number = %d\n", 461 __default_color, PAIR_NUMBER(__default_color)); 462 463 /* Swap red/blue and yellow/cyan */ 464 if (_cursesi_screen->color_type == COLOR_OTHER) { 465 switch (fore) { 466 case COLOR_RED: 467 fore = COLOR_BLUE; 468 break; 469 case COLOR_BLUE: 470 fore = COLOR_RED; 471 break; 472 case COLOR_YELLOW: 473 fore = COLOR_CYAN; 474 break; 475 case COLOR_CYAN: 476 fore = COLOR_YELLOW; 477 break; 478 } 479 switch (back) { 480 case COLOR_RED: 481 back = COLOR_BLUE; 482 break; 483 case COLOR_BLUE: 484 back = COLOR_RED; 485 break; 486 case COLOR_YELLOW: 487 back = COLOR_CYAN; 488 break; 489 case COLOR_CYAN: 490 back = COLOR_YELLOW; 491 break; 492 } 493 } 494 __default_pair.fore = fore; 495 __default_pair.back = back; 496 __default_pair.flags = __USED; 497 498 if (COLOR_PAIRS) { 499 _cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].fore = fore; 500 _cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].back = back; 501 _cursesi_screen->colour_pairs[PAIR_NUMBER(__default_color)].flags = __USED; 502 } 503 504 /* 505 * If we've already called start_color(), make sure all instances 506 * of the curses default colour pair are dirty. 507 */ 508 if (__using_color) 509 __change_pair(PAIR_NUMBER(__default_color)); 510 511 return(OK); 512} 513 514/* no_color_video is a terminfo macro, but we need to retain binary compat */ 515#ifdef __strong_alias 516#undef no_color_video 517__strong_alias(no_color_video, no_color_attributes) 518#endif 519/* 520 * no_color_attributes -- 521 * Return attributes that cannot be combined with color. 522 */ 523attr_t 524no_color_attributes(void) 525{ 526 return(_cursesi_screen->nca); 527} 528 529/* 530 * __set_color -- 531 * Set terminal foreground and background colours. 532 */ 533void 534__set_color( /*ARGSUSED*/ WINDOW *win, attr_t attr) 535{ 536 short pair; 537 538 if ((__do_color_init != 1) && 539 ((curscr->wattr & __COLOR) == (attr & __COLOR))) 540 return; 541 542 pair = PAIR_NUMBER((uint32_t)attr); 543 __CTRACE(__CTRACE_COLOR, "__set_color: %d, %d, %d\n", pair, 544 _cursesi_screen->colour_pairs[pair].fore, 545 _cursesi_screen->colour_pairs[pair].back); 546 switch (_cursesi_screen->color_type) { 547 /* Set ANSI foreground and background colours */ 548 case COLOR_ANSI: 549 if (_cursesi_screen->colour_pairs[pair].fore < 0 || 550 _cursesi_screen->colour_pairs[pair].back < 0) 551 __unset_color(curscr); 552 if (_cursesi_screen->colour_pairs[pair].fore >= 0) 553 tputs(tiparm(t_set_a_foreground(_cursesi_screen->term), 554 (int)_cursesi_screen->colour_pairs[pair].fore), 555 0, __cputchar); 556 if (_cursesi_screen->colour_pairs[pair].back >= 0) 557 tputs(tiparm(t_set_a_background(_cursesi_screen->term), 558 (int)_cursesi_screen->colour_pairs[pair].back), 559 0, __cputchar); 560 break; 561 case COLOR_HP: 562 /* XXX: need to support HP style */ 563 break; 564 case COLOR_TEK: 565 /* XXX: need to support Tek style */ 566 break; 567 case COLOR_OTHER: 568 if (_cursesi_screen->colour_pairs[pair].fore < 0 || 569 _cursesi_screen->colour_pairs[pair].back < 0) 570 __unset_color(curscr); 571 if (_cursesi_screen->colour_pairs[pair].fore >= 0) 572 tputs(tiparm(t_set_foreground(_cursesi_screen->term), 573 (int)_cursesi_screen->colour_pairs[pair].fore), 574 0, __cputchar); 575 if (_cursesi_screen->colour_pairs[pair].back >= 0) 576 tputs(tiparm(t_set_background(_cursesi_screen->term), 577 (int)_cursesi_screen->colour_pairs[pair].back), 578 0, __cputchar); 579 break; 580 } 581 curscr->wattr &= ~__COLOR; 582 curscr->wattr |= attr & __COLOR; 583} 584 585/* 586 * __unset_color -- 587 * Clear terminal foreground and background colours. 588 */ 589void 590__unset_color(WINDOW *win) 591{ 592 __CTRACE(__CTRACE_COLOR, "__unset_color\n"); 593 switch (_cursesi_screen->color_type) { 594 /* Clear ANSI foreground and background colours */ 595 case COLOR_ANSI: 596 if (orig_pair != NULL) { 597 tputs(orig_pair, 0, __cputchar); 598 win->wattr &= __mask_op; 599 } 600 break; 601 case COLOR_HP: 602 /* XXX: need to support HP style */ 603 break; 604 case COLOR_TEK: 605 /* XXX: need to support Tek style */ 606 break; 607 case COLOR_OTHER: 608 if (orig_pair != NULL) { 609 tputs(orig_pair, 0, __cputchar); 610 win->wattr &= __mask_op; 611 } 612 break; 613 } 614} 615 616/* 617 * __restore_colors -- 618 * Redo color definitions after restarting 'curses' mode. 619 */ 620void 621__restore_colors(void) 622{ 623 if (can_change != 0) 624 switch (_cursesi_screen->color_type) { 625 case COLOR_HP: 626 /* XXX: need to re-initialise HP style (Ip) */ 627 break; 628 case COLOR_TEK: 629 /* XXX: need to re-initialise Tek style (Ic) */ 630 break; 631 } 632} 633 634/* 635 * __change_pair -- 636 * Mark dirty all positions using pair. 637 */ 638void 639__change_pair(short pair) 640{ 641 struct __winlist *wlp; 642 WINDOW *win; 643 int y, x; 644 __LINE *lp; 645 uint32_t cl = COLOR_PAIR(pair); 646 647 648 for (wlp = _cursesi_screen->winlistp; wlp != NULL; wlp = wlp->nextp) { 649 __CTRACE(__CTRACE_COLOR, "__change_pair: win = %p\n", 650 wlp->winp); 651 win = wlp->winp; 652 if (win == __virtscr) 653 continue; 654 else if (win == curscr) { 655 /* Reset colour attribute on curscr */ 656 __CTRACE(__CTRACE_COLOR, 657 "__change_pair: win == curscr\n"); 658 for (y = 0; y < curscr->maxy; y++) { 659 lp = curscr->alines[y]; 660 for (x = 0; x < curscr->maxx; x++) { 661 if ((lp->line[x].attr & __COLOR) == cl) 662 lp->line[x].attr &= ~__COLOR; 663 } 664 } 665 } else { 666 /* Mark dirty those positions with colour pair "pair" */ 667 for (y = 0; y < win->maxy; y++) { 668 lp = win->alines[y]; 669 for (x = 0; x < win->maxx; x++) 670 if ((lp->line[x].attr & 671 __COLOR) == cl) { 672 if (!(lp->flags & __ISDIRTY)) 673 lp->flags |= __ISDIRTY; 674 /* 675 * firstchp/lastchp are shared 676 * between parent window and 677 * sub-window. 678 */ 679 if (*lp->firstchp > x) 680 *lp->firstchp = x; 681 682 if (*lp->lastchp < x) 683 *lp->lastchp = x; 684 } 685#ifdef DEBUG 686 if ((win->alines[y]->flags & __ISDIRTY)) 687 __CTRACE(__CTRACE_COLOR, 688 "__change_pair: first = %d, " 689 "last = %d\n", 690 *win->alines[y]->firstchp, 691 *win->alines[y]->lastchp); 692#endif 693 } 694 } 695 } 696} 697