📄 console.c
字号:
} else if (c == 9) { c = 8 - (x & 7); x += c; pos += c << 1; if (x > video_num_columns) { x -= video_num_columns; pos -= video_size_row; lf (); } c = 9;// 如果字符c 是响铃符BEL(7),则调用蜂鸣函数,是扬声器发声。 } else if (c == 7) sysbeep (); break;// 如果原状态是0,并且字符是转义字符ESC(0x1b = 033 = 27),则转到状态1 处理。 case 1: state = 0;// 如果字符c 是'[',则将状态state 转到2。 if (c == '[') state = 2;// 如果字符c 是'E',则光标移到下一行开始处(0 列)。 else if (c == 'E') gotoxy (0, y + 1);// 如果字符c 是'M',则光标上移一行。 else if (c == 'M') ri ();// 如果字符c 是'D',则光标下移一行。 else if (c == 'D') lf ();// 如果字符c 是'Z',则发送终端应答字符序列。 else if (c == 'Z') respond (tty);// 如果字符c 是'7',则保存当前光标位置。注意这里代码写错!应该是(c=='7')。 else if (x == '7') save_cur ();// 如果字符c 是'8',则恢复到原保存的光标位置。注意这里代码写错!应该是(c=='8')。 else if (x == '8') restore_cur (); break;// 如果原状态是1,并且上一字符是'[',则转到状态2 来处理。 case 2:// 首先对ESC 转义字符序列参数使用的处理数组par[]清零,索引变量npar 指向首项,并且设置状态// 为3。若此时字符不是'?',则直接转到状态3 去处理,否则去读一字符,再到状态3 处理代码处。 for (npar = 0; npar < NPAR; npar++) par[npar] = 0; npar = 0; state = 3; if (ques = (c == '?')) break;// 如果原来是状态2;或者原来就是状态3,但原字符是';'或数字,则在下面处理。 case 3:// 如果字符c 是分号';',并且数组par 未满,则索引值加1。 if (c == ';' && npar < NPAR - 1) { npar++; break;// 如果字符c 是数字字符'0'-'9',则将该字符转换成数值并与npar 所索引的项组成10 进制数。 } else if (c >= '' && c <= '9') { par[npar] = 10 * par[npar] + c - ''; break;// 否则转到状态4。 } else state = 4;// 如果原状态是状态3,并且字符不是';'或数字,则转到状态4 处理。首先复位状态state=0。 case 4: state = 0; switch (c) {// 如果字符c 是'G'或'`',则par[]中第一个参数代表列号。若列号不为零,则将光标右移一格。 case 'G': case '`': if (par[0]) par[0]--; gotoxy (par[0], y); break;// 如果字符c 是'A',则第一个参数代表光标上移的行数。若参数为0 则上移一行。 case 'A': if (!par[0]) par[0]++; gotoxy (x, y - par[0]); break;// 如果字符c 是'B'或'e',则第一个参数代表光标下移的行数。若参数为0 则下移一行。 case 'B': case 'e': if (!par[0]) par[0]++; gotoxy (x, y + par[0]); break;// 如果字符c 是'C'或'a',则第一个参数代表光标右移的格数。若参数为0 则右移一格。 case 'C': case 'a': if (!par[0]) par[0]++; gotoxy (x + par[0], y); break;// 如果字符c 是'D',则第一个参数代表光标左移的格数。若参数为0 则左移一格。 case 'D': if (!par[0]) par[0]++; gotoxy (x - par[0], y); break;// 如果字符c 是'E',则第一个参数代表光标向下移动的行数,并回到0 列。若参数为0 则下移一行。 case 'E': if (!par[0]) par[0]++; gotoxy (0, y + par[0]); break;// 如果字符c 是'F',则第一个参数代表光标向上移动的行数,并回到0 列。若参数为0 则上移一行。 case 'F': if (!par[0]) par[0]++; gotoxy (0, y - par[0]); break;// 如果字符c 是'd',则第一个参数代表光标所需在的行号(从0 计数)。 case 'd': if (par[0]) par[0]--; gotoxy (x, par[0]); break;// 如果字符c 是'H'或'f',则第一个参数代表光标移到的行号,第二个参数代表光标移到的列号。 case 'H': case 'f': if (par[0]) par[0]--; if (par[1]) par[1]--; gotoxy (par[1], par[0]); break;// 如果字符c 是'J',则第一个参数代表以光标所处位置清屏的方式:// ANSI 转义序列:'ESC [sJ'(s = 0 删除光标到屏幕底端;1 删除屏幕开始到光标处;2 整屏删除)。 case 'J': csi_J (par[0]); break;// 如果字符c 是'K',则第一个参数代表以光标所在位置对行中字符进行删除处理的方式。// ANSI 转义字符序列:'ESC [sK'(s = 0 删除到行尾;1 从开始删除;2 整行都删除)。 case 'K': csi_K (par[0]); break;// 如果字符c 是'L',表示在光标位置处插入n 行(ANSI 转义字符序列'ESC [nL')。 case 'L': csi_L (par[0]); break;// 如果字符c 是'M',表示在光标位置处删除n 行(ANSI 转义字符序列'ESC [nM')。 case 'M': csi_M (par[0]); break;// 如果字符c 是'P',表示在光标位置处删除n 个字符(ANSI 转义字符序列'ESC [nP')。 case 'P': csi_P (par[0]); break;// 如果字符c 是'@',表示在光标位置处插入n 个字符(ANSI 转义字符序列'ESC [n@')。 case '@': csi_at (par[0]); break;// 如果字符c 是'm',表示改变光标处字符的显示属性,比如加粗、加下划线、闪烁、反显等。// ANSI 转义字符序列:'ESC [nm'。n = 0 正常显示;1 加粗;4 加下划线;7 反显;27 正常显示。 case 'm': csi_m (); break;// 如果字符c 是'r',则表示用两个参数设置滚屏的起始行号和终止行号。 case 'r': if (par[0]) par[0]--; if (!par[1]) par[1] = video_num_lines; if (par[0] < par[1] && par[1] <= video_num_lines) { top = par[0]; bottom = par[1]; } break;// 如果字符c 是's',则表示保存当前光标所在位置。 case 's': save_cur (); break;// 如果字符c 是'u',则表示恢复光标到原保存的位置处。 case 'u': restore_cur (); break; } } }// 最后根据上面设置的光标位置,向显示控制器发送光标显示位置。 set_cursor ();}/** void con_init(void);** This routine initalizes console interrupts, and does nothing* else. If you want the screen to clear, call tty_write with* the appropriate escape-sequece.** Reads the information preserved by setup.s to determine the current display* type and sets everything accordingly.*//** void con_init(void);* 这个子程序初始化控制台中断,其它什么都不做。如果你想让屏幕干净的话,就使用* 适当的转义字符序列调用tty_write()函数。** 读取setup.s 程序保存的信息,用以确定当前显示器类型,并且设置所有相关参数。*/voidcon_init (void){ register unsigned char a; char *display_desc = "????"; char *display_ptr; video_num_columns = ORIG_VIDEO_COLS; // 显示器显示字符列数。 video_size_row = video_num_columns * 2; // 每行需使用字节数。 video_num_lines = ORIG_VIDEO_LINES; // 显示器显示字符行数。 video_page = ORIG_VIDEO_PAGE; // 当前显示页面。 video_erase_char = 0x0720; // 擦除字符(0x20 显示字符, 0x07 是属性)。// 如果原始显示模式等于7,则表示是单色显示器。 if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */ { video_mem_start = 0xb0000; // 设置单显映象内存起始地址。 video_port_reg = 0x3b4; // 设置单显索引寄存器端口。 video_port_val = 0x3b5; // 设置单显数据寄存器端口。// 根据BIOS 中断int 0x10 功能0x12 获得的显示模式信息,判断显示卡单色显示卡还是彩色显示卡。// 如果使用上述中断功能所得到的BX 寄存器返回值不等于0x10,则说明是EGA 卡。因此初始// 显示类型为EGA 单色;所使用映象内存末端地址为0xb8000;并置显示器描述字符串为'EGAm'。// 在系统初始化期间显示器描述字符串将显示在屏幕的右上角。 if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) { video_type = VIDEO_TYPE_EGAM; // 设置显示类型(EGA 单色)。 video_mem_end = 0xb8000; // 设置显示内存末端地址。 display_desc = "EGAm"; // 设置显示描述字符串。 }// 如果BX 寄存器的值等于0x10,则说明是单色显示卡MDA。则设置相应参数。 else { video_type = VIDEO_TYPE_MDA; // 设置显示类型(MDA 单色)。 video_mem_end = 0xb2000; // 设置显示内存末端地址。 display_desc = "*MDA"; // 设置显示描述字符串。 } }// 如果显示模式不为7,则为彩色模式。此时所用的显示内存起始地址为0xb800;显示控制索引寄存// 器端口地址为0x3d4;数据寄存器端口地址为0x3d5。 else /* If not, it is color. */ { video_mem_start = 0xb8000; // 显示内存起始地址。 video_port_reg = 0x3d4; // 设置彩色显示索引寄存器端口。 video_port_val = 0x3d5; // 设置彩色显示数据寄存器端口。// 再判断显示卡类别。如果BX 不等于0x10,则说明是EGA 显示卡。 if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) { video_type = VIDEO_TYPE_EGAC; // 设置显示类型(EGA 彩色)。 video_mem_end = 0xbc000; // 设置显示内存末端地址。 display_desc = "EGAc"; // 设置显示描述字符串。 }// 如果BX 寄存器的值等于0x10,则说明是CGA 显示卡。则设置相应参数。 else { video_type = VIDEO_TYPE_CGA; // 设置显示类型(CGA)。 video_mem_end = 0xba000; // 设置显示内存末端地址。 display_desc = "*CGA"; // 设置显示描述字符串。 } }/* Let the user known what kind of display driver we are using *//* 让用户知道我们正在使用哪一类显示驱动程序 */// 在屏幕的右上角显示显示描述字符串。采用的方法是直接将字符串写到显示内存的相应位置处。// 首先将显示指针display_ptr 指到屏幕第一行右端差4 个字符处(每个字符需2 个字节,因此减8)。 display_ptr = ((char *) video_mem_start) + video_size_row - 8;// 然后循环复制字符串中的字符,并且每复制一个字符都空开一个属性字节。 while (*display_desc) { *display_ptr++ = *display_desc++; // 复制字符。 display_ptr++; // 空开属性字节位置。 }/* Initialize the variables used for scrolling (mostly EGA/VGA) *//* 初始化用于滚屏的变量(主要用于EGA/VGA) */ origin = video_mem_start; // 滚屏起始显示内存地址。 scr_end = video_mem_start + video_num_lines * video_size_row; // 滚屏结束内存地址。 top = 0; // 最顶行号。 bottom = video_num_lines; // 最底行号。 gotoxy (ORIG_X, ORIG_Y); // 初始化光标位置x,y 和对应的内存位置pos。 set_trap_gate (0x21, &keyboard_interrupt); // 设置键盘中断陷阱门。 outb_p (inb_p (0x21) & 0xfd, 0x21); // 取消8259A 中对键盘中断的屏蔽,允许IRQ1。 a = inb_p (0x61); // 延迟读取键盘端口0x61(8255A 端口PB)。 outb_p (a | 0x80, 0x61); // 设置禁止键盘工作(位7 置位), outb (a, 0x61); // 再允许键盘工作,用以复位键盘操作。}/* from bsd-net-2: *///// 停止蜂鸣。// 复位8255A PB 端口的位1 和位0。voidsysbeepstop (void){/* disable counter 2 *//* 禁止定时器2 */ outb (inb_p (0x61) & 0xFC, 0x61);}int beepcount = 0;// 开通蜂鸣。// 8255A 芯片PB 端口的位1 用作扬声器的开门信号;位0 用作8253 定时器2 的门信号,该定时器的// 输出脉冲送往扬声器,作为扬声器发声的频率。因此要使扬声器蜂鸣,需要两步:首先开启PB 端口// 位1 和位0(置位),然后设置定时器发送一定的定时频率即可。static voidsysbeep (void){/* enable counter 2 *//* 开启定时器2 */ outb_p (inb_p (0x61) | 3, 0x61);/* set command for counter 2, 2 byte write *//* 送设置定时器2 命令 */ outb_p (0xB6, 0x43);/* send 0x637 for 750 HZ *//* 设置频率为750HZ,因此送定时值0x637 */ outb_p (0x37, 0x42); outb (0x06, 0x42);/* 1/8 second *//* 蜂鸣时间为1/8 秒 */ beepcount = HZ / 8;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -