/* vi: set sw=4 ts=4: */ /* * KON2 - Kanji ON Console - * Copyright (C) 1992-1996 Takashi MANABE (manabe@papilio.tutics.tut.ac.jp) * * CCE - Console Chinese Environment - * Copyright (C) 1998-2003 Rui He (rhe@3eti.com) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE TERRENCE R. LAMBERT BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* Console driver handling, may have a look at CCE 0.11's console.c and Linux kernel source code: /usr/src/linux/drivers/char/console.c The below code doesn't handle all of the ANSI required ESC stuff */ #include #include #include #include #include #include #include #include #include #include #include #include #define CHAR_NUL '\x00' #define CHAR_BEL '\x07' #define CHAR_BS '\x08' #define CHAR_HT '\x09' #define CHAR_LF '\x0A' #define CHAR_VT '\x0B' #define CHAR_FF '\x0C' #define CHAR_CR '\x0D' #define CHAR_SO '\x0E' #define CHAR_SI '\x0F' #define CHAR_XON '\x11' #define CHAR_XOFF '\x12' #define CHAR_CAN '\x18' #define CHAR_SUB '\x1A' #define CHAR_ESC '\x1B' #define CHAR_DEL '\x7F' #define CHAR_CSI '\x9B' #define CHAR_SS2 '\x8E' #define LEN_REPORT 9 static int scroll; LangInfo lInfo; int OverrideSysCoding; /* override the configuration file settings */ static void SaveAttr(ConInfo *con) { struct attrStack *tmp; tmp = (struct attrStack *)malloc(sizeof(struct attrStack)); if (con->saveAttr) tmp->prev = con->saveAttr; else tmp->prev = NULL; con->saveAttr = tmp; con->saveAttr->x = con->x; con->saveAttr->y = con->y; con->saveAttr->attr = con->attr; con->saveAttr->charfont = con->charfont; con->saveAttr->fcol = con->fcol; con->saveAttr->bcol = con->bcol; } static void RestoreAttr(ConInfo *con) { struct attrStack *tmp; if (con->saveAttr) { con->x = con->saveAttr->x; con->y = con->saveAttr->y; con->attr = con->saveAttr->attr; con->fcol = con->saveAttr->fcol; con->bcol = con->saveAttr->bcol; con->charfont = con->saveAttr->charfont; tmp = con->saveAttr; con->saveAttr = tmp->prev; free(tmp); } } static void UpdateAttr(ConInfo *con) { u_char swp; if (con->attr & ATTR_ULINE) con->fcol = 0xf; /* ulcolor */ /* bold white */ else if (con->attr & ATTR_LOW) con->fcol = 0x8; /* halfcolor */ /* grey */ if (con->attr & ATTR_REVERSE) { swp = con->fcol; con->fcol = (con->fcol & 8) | (con->bcol & 7); con->bcol = (con->bcol & 8) | (swp & 7); } if (con->attr & ATTR_BLINK) con->bcol |= 0x8; /* ^= 0x8; */ if (con->attr & ATTR_HIGH) con->fcol |= 0x8; /* ^= 0x8; */ } static void EscSetAttr(ConInfo *con,int col) { static u_char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15 }; u_char swp; switch(col) { case 0: /* off all attributes */ con->bcol = DEF_BGCOLOR; con->fcol = DEF_FGCOLOR; con->charfont = 0; con->attr = 0; break; case 1: /* highlight, fcolor intensity = 2 Turns on bold mode */ con->attr = (con->attr & ~ATTR_LOW) | ATTR_HIGH; break; case 2: /* intensity = 0 halfcolor 0x08 grey Set foreground color? CSI 2;M;N m */ con->attr = (con->attr & ~ATTR_HIGH) | ATTR_LOW; break; case 3: /* italic/blink mode SCO-ish? enable italic mode */ case 23: /* disable italic mode */ break; case 4: /* underline, bcolor linux kernel using ulcolor(0x0f) bold white for underline */ con->attr |= ATTR_ULINE; break; case 5: /* blink=1 enable blinking */ case 26: con->attr |= ATTR_BLINK; break; case 6: /* blink=0 disable blinking */ case 25: con->attr &= ~ATTR_BLINK; break; case 7: /* reverse reverse=1 */ con->attr |= ATTR_REVERSE; break; case 21: /* clear highlight 21-disable bold mode */ case 22: /* 21,22 intensity=1 */ con->attr &= ~(ATTR_HIGH | ATTR_LOW); con->fcol &= ~8; break; case 24: /* clear underline underline=0 */ con->attr &= ~ATTR_ULINE; con->fcol &= ~8; break; case 27: /* clear reverse reverse=0 */ if (con->attr & ATTR_REVERSE) { con->attr &= ~ATTR_REVERSE; swp = con->fcol; con->fcol = (con->fcol & 8) | (con->bcol & 7); con->bcol = (con->bcol & 8) | (swp & 7); } break; case 10: /* ANSI X3.64-1979 (SCO-ish?) * Select primary font, don't display * control chars if defined, don't set * bit 8 on output. */ con->charfont = 0; break; case 11: /* ANSI X3.64-1979 (SCO-ish?) * Select first alternate font, lets * chars < 32 be displayed as ROM chars. */ con->charfont = 1; break; case 12: /* ANSI X3.64-1979 (SCO-ish?) * Select second alternate font, toggle * high bit before displaying as ROM char. */ con->charfont = 2; break; case 38: /* ANSI X3.64-1979 (SCO-ish?) * Enables underscore, white foreground * with white underscore (Linux - use ** default foreground). */ con->fcol = DEF_FGCOLOR; con->attr |= ATTR_ULINE; break; case 39: /* ANSI X3.64-1979 (SCO-ish?) * Disable underline option. * Reset colour to default? It did this before... */ con->fcol = DEF_FGCOLOR; con->attr &= ~ATTR_ULINE; break; case 49: /* background use default, foregroud use old values */ con->bcol = DEF_BGCOLOR; break; default: if (col >= 30 && col <= 37) /* assign foreground color */ { con->fcol = color_table[col-30]; } else if (col >= 40 && col <= 47) /* assign background color */ { con->bcol = color_table[col-40]; } break; } } static void VtSetMode(ConInfo *con,u_char mode, bool sw) { switch(mode) { case 3: /* Monitor (display ctrls) */ break; case 4: /* Insert Mode on/off */ con->ins = sw; break; case 7: /* autowrap on/off */ break; case 20: /* Lf, Enter = CrLf/Lf */ break; case 25: /* cursor on/off */ cursorInfo.sw = sw; break; } } static void EscReport(ConInfo *con,u_char mode, u_short arg) { static char report[LEN_REPORT]; switch(mode) { case 'n': if (arg == 6) /* cursor report */ { int x = (con->x < con->xmax) ? con->x : con->xmax; int y = (con->y < con->ymax) ? con->y : con->ymax; snprintf(report, LEN_REPORT, "\x1B[%d;%dR", y + 1, x + 1); } else if (arg == 5) /* status report */ strcpy(report, "\x1B[0n\0"); /* ok */ break; case 'c': if (arg == 0) strcpy(report, "\x1B[?6c\0"); /* ok */ else return; break; } write(con->masterPty, report, strlen(report)); } #define MAX_NARG 8 static void EscBracketEqual(ConInfo *con, u_char ch) { u_char n; static u_short varg[MAX_NARG+1], narg; if (ch >= '0' && ch <= '9') { varg[narg] = (varg[narg] * 10) + (ch - '0'); } else if (ch == ';') { if (narg < MAX_NARG) narg++; else con->esc = NULL; } else { con->esc = NULL; switch(ch) { case 'c': cursorInfo.sw = (varg[0] != 0); break; } if (con->esc == NULL) { narg = 0; for ( n = 0; n < MAX_NARG; n++ ) varg[n] = 0; } } } static void EscBracket(ConInfo *con,u_char ch) { u_char n; static u_short varg[MAX_NARG+1], narg, question; if (ch >= '0' && ch <= '9') { varg[narg] = (varg[narg] * 10) + (ch - '0'); } else if (ch == ';') { if (narg < MAX_NARG) narg++; else con->esc = NULL; } else { con->esc = NULL; switch(ch) { case '=': /* Feb 12, 2004 added for FreeBSD's vim, SCO-specific */ question = narg = 0; for ( n = 0; n < MAX_NARG; n++ ) varg[n] = 0; con->esc = EscBracketEqual; break; case 'K': /* bash use this all the time ESC(0x1B) [(0x5B) K(0x4B) ^M(0x0D) */ TextClearEol(con,varg[0]); /* 0x0 */ break; case 'J': /* linux kernel csi_J() */ TextClearEos(con,varg[0]); break; case 'X': /* Erase n characters in line */ TextClearChars(con, varg[0]); break; case 'S': /* Scroll up n lines */ scroll += varg[0] ? varg[0] : 1; break; case 'T': /* Scroll down n lines */ scroll -= varg[0] ? varg[0] : 1; break; case 'F': /* UP, CR */ con->x = 0; case 'A': /* UP ESC(0x1B) [(0x5B) A(0x41) */ con->y -= varg[0] ? varg[0]: 1; if (con->y < con->ymin) { scroll -= con->y - con->ymin; con->y = con->ymin; } break; case 'E': /* DOWN, CR */ con->x = 0; case 'B': /* DOWN */ case 'e': con->y += varg[0] ? varg[0]: 1; if (con->y > con->ymax) { scroll += con->y - con->ymin; con->y = con->ymax; } break; case 'C': /* RIGHT */ case 'a': con->x += varg[0] ? varg[0]: 1; con->wrap = FALSE; break; case 'D': /* LEFT */ con->x -= varg[0] ? varg[0]: 1; con->wrap = FALSE; break; case 'G': /* cursor horizontal movement */ case '`': con->x = varg[0] ? varg[0] - 1: 0; con->wrap = FALSE; break; case 'P': /* Delete n chars */ TextDeleteChar(con,varg[0] ? varg[0]: 1); break; case '@': /* Insert n chars */ TextInsertChar(con,varg[0] ? varg[0]: 1); break; case 'L': /* Insert n lines */ TextMoveDown(con, con->y, con->ymax, varg[0] ? varg[0] : 1); break; case 'M': /* Delete n lines */ TextMoveUp(con, con->y, con->ymax, varg[0] ? varg[0] : 1); break; case 'H': case 'f': /* move cursor to (m,n) */ if (varg[1]) con->x = varg[1] - 1; else con->x = 0; con->wrap = FALSE; /* Attention: no break! */ case 'd': /* move cursor to (n) */ con->y = varg[0] ? varg[0] - 1: 0; break; case 'm': if (question != TRUE) { for (n = 0; n <= narg; n ++) EscSetAttr(con,varg[n]); UpdateAttr(con); } break; case 'r': /* set scroll range */ con->ymin = varg[0]? (varg[0] - 1): 0; con->ymax = varg[1]? (varg[1] - 1): (dispInfo.tymax - 1); /* Fixed by Holly Lee - Sep 26, 1999 */ con->x = 0; con->y = con->ymin; con->wrap = FALSE; if (con->ymin || con->ymax != dispInfo.tymax) con->soft = TRUE; else con->soft = FALSE; break; case 'l': for (n = 0; n <= narg; n ++) VtSetMode(con,varg[n], FALSE); break; case 'h': for (n = 0; n <= narg; n ++) VtSetMode(con,varg[n], TRUE); break; case '?': question = TRUE; con->esc = EscBracket; break; case 's': SaveAttr(con); break; case 'u': RestoreAttr(con); break; case 'n': if (question != TRUE) EscReport(con,ch, varg[0]); break; case 'c': #if defined(__SCOUnixWare__) /* ESC [ 0-2 c for show/hide cursor */ if (question != TRUE) cursorInfo.sw = (varg[0] != 2); #else /* Linux-specific? */ if (question != TRUE) EscReport(con, ch, varg[0]); #endif break; case 'R': break; } if (con->esc == NULL) { question = narg = 0; /* reset question mark */ for ( n = 0; n < MAX_NARG; n++ ) varg[n] = 0; } } } static void EscSetDCodeG0(ConInfo *con,u_char ch) { int i; switch(ch) { case '(': /* EscSetDCodeG0 */ case ')': /* EscSetDCodeG1 */ return; case '@': ch = 'B'; /* For Japanese, @->B */ default: i = 0; while (fDRegs[i].sign0) { if (fDRegs[i].sign0 == ch) { con->db = (u_char)i|LATCH_1; break; } i++; } con->trans = CS_DBCS; break; } con->esc = NULL; } static void EscSetSCodeG0(ConInfo *con,u_char ch) { int i=0; switch(ch) { case 'U': con->g[0] = CS_GRAPH; break; default: while (fSRegs[i].sign0) { if (fSRegs[i].sign0 == ch) { con->sb = (u_char)i; con->g[0] = CS_LEFT; break; } else if (fSRegs[i].sign1 == ch) { con->sb = (u_char)i; con->g[0] = CS_RIGHT; break; } i++; } } con->trans = con->g[0]; con->esc = NULL; } static void EscSetSCodeG1(ConInfo *con,u_char ch) { switch(ch) { case 'U': con->g[1] = CS_LEFT; break; case '0': con->g[1] = CS_GRAPH; break; case 'A': case 'J': /* (J from ISO-2022-JP JIS back to ASCII */ case 'B': /* $B from ASCII to ISO-2022-JP JIS */ case 'C': /* $)C ISO-2022-KR (0x0E/0x0F shift-in/out) */ break; } con->trans = con->g[1]; con->esc = NULL; } static void EscStart(ConInfo *con,u_char ch) { con->esc = NULL; switch(ch) { case '[': con->esc = EscBracket; break; case '$':/* Set Double Byte Code */ con->esc = EscSetDCodeG0; break; case '(':/* Set 94 to G0 */ con->esc = EscSetSCodeG0; break; case ')':/* Set G1 */ con->esc = EscSetSCodeG1; break; case 'E': /* supposed to be cr() lf() */ con->x = 0; con->wrap = FALSE; case 'D': if (con->y == con->ymax) scroll++; else con->y++; break; case 'I': /* beginning of previous line, scroll up */ con->x = 0; con->wrap = FALSE; case 'M': /* cursor reverse index, scroll up */ if (con->y == con->ymin) scroll--; else con->y--; break; case 'c': /* reset terminal & clear screen? */ con->fcol = DEF_FGCOLOR; con->attr = DEF_BGCOLOR; con->charfont = 0; con->knj1 = con->bcol = 0; con->wrap = FALSE; con->trans = CS_LEFT; con->sb = lInfo.sb; con->db = lInfo.db|LATCH_1; case '*': con->x = con->y = 0; con->wrap = FALSE; TextClearAll(con); break; case '7': /* Save cursor position & other attributes(as in Linux) */ SaveAttr(con); break; case '8': /* Restore cursor position */ RestoreAttr(con); con->wrap = FALSE; break; case ']': break; } } int isPrevW1(ConInfo *con) { u_int addr; if (con->x <= 0) return 0; addr = TextAddress(con,con->x-1, con->y); return(*(con->flagBuff + addr) & LATCH_1); } void VtWrite(ConInfo *con, const u_char *buff, int nchars) { u_char ch; while (nchars-- > 0) { ch = *buff++; if (! ch) continue; if (con->esc) con->esc(con,ch); /* in escape mode, parse the parameters */ else switch (ch) { case CHAR_BEL: /* 0x07 ^G */ Beep(); break; case CHAR_DEL: /* 0x7F */ break; case CHAR_BS: /* 0x08 ^H BackSpace */ if (con->x) con->x--; con->wrap = FALSE; break; case CHAR_HT: /*0x09 ^I Horizontal Tab */ con->x += con->tab - (con->x % con->tab); con->wrap = FALSE; if (con->x > con->xmax) con->x -= con->xmax + 1; else break; case CHAR_FF: /* 0x0C ^L Form Feed (clear screen and move cursor to topleft) */ con->x = con->y = 0; con->wrap = FALSE; TextClearAll(con); break; case CHAR_VT: /* 0x0B ^K Vertial Tab (same x location, but next line) */ case CHAR_LF: /* 0x0A ^J Line Feed */ con->wrap = FALSE; if (con->y == con->ymax) scroll++; else con->y++; break; case CHAR_CR: /* 0x0D ^M Carriage Return */ con->x = 0; con->wrap = FALSE; break; case CHAR_ESC: /* 0x1B */ con->esc = EscStart; continue; #if defined(__SCO__) case CHAR_CSI: /* case 128+27: 0x9B Esc->SQUARE */ con->esc = EscBracket; break; #endif case CHAR_SO: /* 0x0E ^N Shift Out using G1 charset (charset=1) */ con->trans = con->g[1] | G1_SET; continue; case CHAR_SI: /* 0x0F ^O Shift In using G0 charset */ con->trans = con->g[0]; continue; default: #if defined(__SCO__) /* Rui He Jan 18, 2004 added for SCO's table line stuff */ if (con->charfont == 2) /* toggle_meta, turn on bit 7 before display */ { #if defined(__SCOUnixWare__) static unsigned char ISO8859ToCP437Map[] = { 217, 191, 218, 192, 197, 32, 32, 196, 32, 32, 195, 180, 193, 194, 179}; ch |= 0x80; if (ch >= 139 && ch <= 153) ch = ISO8859ToCP437Map[ch - 139]; #else /* otherwise __SCOOpenServer__ */ ch |= 0x80; #endif } #endif if (con->x == con->xmax + 1) { con->wrap = TRUE; con->x --; } if (con->wrap) { con->x -= con->xmax; if (con->y == con->ymax) scroll ++; else con->y ++; con->wrap = FALSE; buff --; nchars ++; break; } if (con->knj1) /* First Part of Hanzi in memory ? */ { if (con->knj1 & 0x80) { switch(lInfo.sysCoding) { case CODE_EUC: /* CHAR_SS2 followed by a kata-kana(0xA0-DF) */ if (con->knj1 == (u_char)CHAR_SS2) /* CHAR_SS2 '\x8E' */ { if (con->ins) TextInsertChar(con,1); TextSput(con,ch); con->x ++; con->knj1 = 0; continue; } break; case CODE_SJIS: sjistojis(con->knj1, ch); /* always store JIS */ break; } /* case */ } if (con->ins) TextInsertChar(con,2); TextWput(con,con->knj1, ch); con->x += 2; con->knj1 = 0; continue; } /* if knj1 */ else if ((con->charfont== 0) && (con->trans == CS_DBCS || (iskanji_byte1(lInfo.sysCoding, ch) && con->trans == CS_LEFT))) { if (nchars > 0 && iskanji_byte2(lInfo.sysCoding, *buff)) /* added by Rui He Jul 3 2003, test the 2nd byte */ { if (con->x == con->xmax) con->wrap = TRUE; /* xmax is something like 79 */ con->knj1 = ch; continue; } else /* next one is not kanji ? */ { if ( (nchars > 2) && (buff[0] == 0x0D) && (buff[1] == 0x0A) && iskanji_byte2(lInfo.sysCoding, buff[2])) con->knj1 = ch; else { if (isPrevW1(con) && iskanji_byte2(lInfo.sysCoding, ch)) /* previous one is Kanji byte1, this one should be byte2? */ { if (con->ins) TextInsertChar(con,1); TextWput2(con, ch); } else /* output as the first char */ { if (con->ins) TextInsertChar(con,1); TextWput1(con, ch); /* Rui He Jul 9 2003, should we output LATCH_1 ? */ } con->x++; continue; } } } else { if (con->ins) TextInsertChar(con,1); TextSput(con,con->trans == CS_RIGHT ? ch | 0x80: ch); con->x++; continue; } } /* switch */ if (scroll > 0) ScrollUp(con, scroll); else if (scroll < 0) ScrollDown(con, - scroll); scroll = 0; } /* while */ } extern int CodingByRegistry(char *reg); static int ConfigCoding(const char *confstr) { char reg[3][MAX_COLS]; int n, i; *reg[0] = *reg[1] = *reg[2] = '\0'; sscanf(confstr, "%255s %255s %255s", reg[0], reg[1], reg[2]); for (i = 0; i < 2 && *reg[i]; i ++) { n = (int)CodingByRegistry(reg[i]); if (n < 0) /* Rui He Jul 11, 2003 */ fatal("Unrecognized coding registry %s\r\n", reg[i]); else if (n & CHR_DBC) lInfo.db = n & ~CHR_DFLD; else lInfo.sb = n & ~CHR_SFLD; } if (OverrideSysCoding == -1) /* read default settings from cce.cfg */ { lInfo.sysCoding = CODE_GB; /* default one */ if (*reg[2]) /* Coding Exists */ { if (!strcasecmp(reg[i], "EUC")) lInfo.sysCoding = CODE_EUC; else if (!strcasecmp(reg[i], "SJIS")) lInfo.sysCoding = CODE_SJIS; else if (!strcasecmp(reg[i], "BIG5")) lInfo.sysCoding = CODE_BIG5; else if (!strcasecmp(reg[i], "GB")) lInfo.sysCoding = CODE_GB; else if (!strcasecmp(reg[i], "GBK")) lInfo.sysCoding = CODE_GBK; else if (!strcasecmp(reg[i], "KSC")) lInfo.sysCoding = CODE_KSC; } } else lInfo.sysCoding = OverrideSysCoding; switch(lInfo.sysCoding) { case CODE_GB: DefineCap("InputMethodGB", ConfigInputMethod, NULL); break; case CODE_BIG5: DefineCap("InputMethodBig5", ConfigInputMethod, NULL); break; case CODE_GBK: DefineCap("InputMethodGBK", ConfigInputMethod, NULL); break; case CODE_EUC: case CODE_SJIS: DefineCap("InputMethodJIS", ConfigInputMethod, NULL); break; case CODE_KSC: DefineCap("InputMethodKSC", ConfigInputMethod, NULL); break; } return SUCCESS; } void VtInit(void) { DefineCap("Coding", ConfigCoding, "ISO8859-1 GB2312.1980-0 GB"); } void VtStart(ConInfo *con) { con->x = con->y = 0; con->xmax = dispInfo.txmax-1; con->ymax = dispInfo.tymax-1; con->ymin = 0; con->tab = 8; con->charfont = 0; con->fcol = DEF_FGCOLOR; con->bcol = DEF_BGCOLOR; con->attr = 0; con->esc = NULL; con->g[0] = con->g[1] = CS_LEFT; con->trans = con->soft = con->ins = con->wrap = FALSE; con->sb = lInfo.sb; con->db = lInfo.db|LATCH_1; con->knj1 = 0; cursorInfo.sw = TRUE; con->textBuff = (u_char *)calloc(dispInfo.txmax, dispInfo.tymax); con->attrBuff = (u_char *)calloc(dispInfo.txmax, dispInfo.tymax); con->flagBuff = (u_char *)calloc(dispInfo.txmax, dispInfo.tymax); if (!con->textBuff || !con->attrBuff || !con->flagBuff) fatal("Failed to allocate new window buffer, textBuff=%p, attrBuff=%p, flagBuff=%p\n", con->textBuff, con->attrBuff, con->flagBuff); con->textSize = (dispInfo.txmax) * (dispInfo.tymax); con->currentScroll = con->scrollLine = 0; con->textHead = 0; con->textClear = FALSE; con->terminate = 0; } void VtCleanup(ConInfo *con) { if (con == NULL) return; con->scrollLine = con->textHead = con->currentScroll = 0; }