自制操作系统日志——第二十四天
今天呢,我们主要进行窗口的操作,诸如进行窗口切换,移动窗口,用鼠标切换窗口等等,下面让我们正式开始吧!
一、窗口切换
我们先来个简单的窗口切换,练练手吧!按下F11,将最底层的图层放置最上层。
F11 ⇒ 0x57
bootpack.c:
if(i == 256 + 0x57 && shtctl->top > 2){//F11
sheet_updown(shtctl->sheets[1], shtctl->top -1); //将除背景外的最底层放到鼠标图层的下一层
}
这确实没啥难度了。
下面,让我们使用鼠标点击图层进行显示。当鼠标进行点击时候,我们需要按照从上到下的顺序进行判断,鼠标落在哪一个图层的范围里:
bootpack.c:主函数:
略
int j, x, y;
struct SHEET *sht;
略
if((mdec.btn & 0x01) != 0)//按下左键
{
//按照从上到下的顺序寻找鼠标所指向的图层
for(j = shtctl->top -1; j > 0; j--){
sht = shtctl->sheets[j];
x = mx - sht->vx0;
y = my - sht->vy0;
if(0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize){//利用差值判断鼠标是否在该图层之中
if(sht->buf[y * sht->bxsize + x] != sht->col_inv){//判断是不是透明色,不是透明色就进行刷新
sheet_updown(sht, shtctl->top - 1);
break;
}
}
}
}
喔喔,成功了嘿嘿嘿!
二、移动窗口
其实,我们之前已经实现过一部分的窗口移动了。那么接下来,我们就在之前的基础上改造一下咯:
当鼠标点击标题栏时候,就进入到窗口移动模式,当鼠标松开左键后,就推出窗口的移动模式,返回通常模式。 除此之外,还需要新增两个变量,mmx、mmy,用于记录移动之前的坐标。并结合mx、my来计算鼠标移动的距离。
else if (512 <= i && i <= 767) { /* 鼠标数据 */
if (mouse_decode(&mdec, i - 512) != 0) {
/* 鼠标的移动 */
mx += mdec.x;
my += mdec.y;
if (mx < 0) {
mx = 0;
}
if (my < 0) {
my = 0;
}
if (mx > binfo->scrnx - 1) {
mx = binfo->scrnx - 1;
}
if (my > binfo->scrny - 1) {
my = binfo->scrny - 1;
}
sheet_slide(sht_mouse, mx, my);
if((mdec.btn & 0x01) != 0)//按下左键
{
if(mmx < 0){
//按照从上到下的顺序寻找鼠标所指向的图层
for (j = shtctl->top - 1; j > 0; j--) {
sht = shtctl->sheets[j];
x = mx - sht->vx0;
y = my - sht->vy0;
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {//利用差值判断鼠标是否在该图层之中
if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {
sheet_updown(sht, shtctl->top - 1);
if(3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21){
//窗口移动模式
mmx = mx;
mmy = my;
}
break;
}
}
}
}else{
//处于窗口移动模式
x = mx - mmx;//计算鼠标移动模式
y = my - mmy;
sheet_slide(sht, sht->vx0 + x, sht->vy0 + y);
mmx = mx;//更新为移动后的位置
mmy = my;
}
}else{
//没有按下左键
mmx = -1;
}
}
}
然后运行:
看看,真的成功了,嘿嘿嘿!!!其实仔细看看这一段代码也不是很难的,是吧? 是的!!!
三、用鼠标关闭窗口
emm,其实这一部分也不难,首先就是鼠标移到x这个地方的判断和上述几个是一样的。至于关闭程序呢,就和前面的强制结束程序代码是一样的:
if(sht->bxsize -21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19){
//点击X按钮
if(sht->task != 0){
cons = (struct CONSOLE *) *((int *) 0xfec);
cons_putstr0(cons, " \nBreak(mouse):\n");
io_cli();
task_cons->tss.eax = (int) &(task_cons->tss.esp0);
task_cons->tss.eip = (int) asm_end_app;
io_sti();
}
}
break;
}
}
试试看:
点击一下,确实是关闭了,嘿嘿嘿!!不过目前我们只做了关闭程序的命令,至于命令行和其他图层窗口的关闭后续还会接着讲喔。
四、将输入切换到应用程序窗口
我们先制作利用tab进行切换。当我们按下tab时候,键盘输入切换到当前输入窗口的下一次窗口之中,若该窗口是最下层则切换到最上层。
首先,我们设置启用自动关闭窗口的功能,为了区分,我们将应用程序的flags值与0x10进行OR运算,使其成为0x1[] :
console.c:
hrb_api
else if (edx == 5){
sht = sheet_alloc(shtctl);
sht->task = task;
sht->flags |= 0x10;//启动应用程序自动关闭窗口的功能
cmd_app
for(i = 0; i <MAX_SHEETS; i++){
sht = &(shtctl->sheets0[i]);
if((sht->flags & 0x11) == 0x11 && sht->task == task)
{
//找到被应用程序遗留的窗口
sheet_free(sht);//关闭
}
}
然后,我们需要制作一个改变窗口标题栏颜色的函数:
window.c
//改变标题栏的颜色
void change_wtitle8(struct SHEET *sht, char act)
{
int x, y, xsize = sht->bxsize;
char c, tc_new, tbc_new, tc_old, tbc_old, *buf = sht->buf;
if (act != 0) {
tc_new = COL8_FFFFFF;
tbc_new = COL8_000084;
tc_old = COL8_C6C6C6;
tbc_old = COL8_848484;
} else {
tc_new = COL8_C6C6C6;
tbc_new = COL8_848484;
tc_old = COL8_FFFFFF;
tbc_old = COL8_000084;
}
for (y = 3; y <= 20; y++) {
for (x = 3; x <= xsize - 4; x++) {
c = buf[y * xsize + x];
if (c == tc_old && x <= xsize - 22) {
c = tc_new;
} else if (c == tbc_old) {
c = tbc_new;
}
buf[y * xsize + x] = c;
}
}
sheet_refresh(sht, 3, 3, xsize, 21);
return;
}
然后修改一下主函数里的内容:
bootpack.c:
int key_shift = 0, key_leds = (binfo->leds >> 4) & 7, keycmd_wait = -1;
int j, x, y, mmx = -1, mmy = -1;//mm 记录鼠标移动之前的坐标;由于鼠标指针不会移到画面外,因此设置-1表不移动状态
struct SHEET *sht = 0, *key_win;
略
key_win = sht_win;//用key_win存放当前窗口的地址
sht_cons->task = task_cons;
sht_cons->flags |= 0x20; //有光标
略
if( s[0] != 0)//一般字符
{
if(key_win == sht_win){
//发送给任务a
if( cursor_x < 128){
//显示一个字符光标就向后移动一次
s[1] = 0;
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_C6C6C6, s, 1);
cursor_x += 8;
}
}else{
//发送给命令行
fifo32_put(&task_cons->fifo, s[0] + 256);
}
}
if(i == 256 + 0x0e ) //退格键
{//用空格把光标消去后移动一次
if(key_win == sht_win){
if(cursor_x > 8){
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
cursor_x -= 8;
}
}else{
fifo32_put(&key_win->task->fifo, 8 + 256);//z这里定义在console中退格键的编码为8
}
}
if(i == 256 + 0x1c){//Enter
if(key_win != sht_win){//发送到命令行窗口
fifo32_put(&key_win->task->fifo, 10 + 256);
}
}
if(i == 256 + 0x0f){//tab键
cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
j = key_win->height - 1;
if (j == 0) {
j = shtctl->top - 1;
}
key_win = shtctl->sheets[j];
cursor_c = keywin_on(key_win, sht_win, cursor_c);
}
最后,在添加一下控制窗口标题栏颜色和task_a光标的函数:
//控制窗口标题栏的颜色和task_a的光标
int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x)
{
change_wtitle8(key_win, 0);
if (key_win == sht_win) {
cur_c = -1; /* 删除光标 */
boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43);
} else {
if ((key_win->flags & 0x20) != 0) {
fifo32_put(&key_win->task->fifo, 3); /* 命令行窗口光标off */
}
}
return cur_c;
}
int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c)
{
change_wtitle8(key_win, 1);
if (key_win == sht_win) {
cur_c = COL8_000000; /* 显示光标 */
} else {
if ((key_win->flags & 0x20) != 0) {
fifo32_put(&key_win->task->fifo, 2); /* 命令行窗口光标ON*/
}
}
return cur_c;
}
用key_win变量存放当前处于输入模式的窗口地址。
那么,进一步的我们使用鼠标进行切换输入窗口吧!
if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {//利用差值判断鼠标是否在该图层之中
if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {
sheet_updown(sht, shtctl->top - 1);
if(sht != key_win)
{
cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
key_win = sht;
cursor_c = keywin_on(key_win, sht_win,cursor_c);
}
改变一点点即可:
定时器API
接下来,让我们再制作一个定时器的API:
获取定时器(alloc):
- EDX = 16
- EAX = 定时器句柄(由操作系统进行返回的)
设置定时器发送的数据(init):
- EDX = 17
- EBX = 定时器句柄
- EAX = 数据
定时器设定(set):
- EDX = 18
- EBX = 定时器句柄
- EAX = 时间
释放定时器(free):
- EDX = 19
- EBX = 定时器句柄
console.c:
else if (edx == 15){
略
if( i >= 256 ){//键盘数据,通过A,接收256以上的数据,即除了键盘数据外还接受定时器发送的数据
reg[7] = i - 256;
return 0;
}
}
}else if (edx == 16) {
reg[7] = (int) timer_alloc();
} else if (edx == 17) {
timer_init((struct TIMER *) ebx, &task->fifo, eax + 256);
} else if (edx == 18) {
timer_settime((struct TIMER *) ebx, eax);
} else if (edx == 19) {
timer_free((struct TIMER *) ebx);
}
再写一个程序,用于显示时间的,这个程序的定时器超时时会发送128的值,传递到缓冲区时会+256的。 当我们的数据不是128时,证明系统收到了其他的数据:
noodle.c
#include <stdio.h>
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_end(void);
void HariMain(void)
{
char *buf, s[12];
int win, timer, sec = 0, min = 0, hou = 0;
api_initmalloc();
buf = api_malloc(150 * 50);
win = api_openwin(buf, 150, 50, -1, "noodle");
timer = api_alloctimer();
api_inittimer(timer, 128);
for (;;) {
sprintf(s, "%5d:%02d:%02d", hou, min, sec);
api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色 */);
api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s);
api_settimer(timer, 100); /* 1秒*/
if (api_getkey(1) != 128) {
break;
}
sec++;
if (sec == 60) {
sec = 0;
min++;
if (min == 60) {
min = 0;
hou++;
}
}
}
api_end();
}
运行看看:
嚯嚯,成功!
emm 等等好像有bug,当我们点击关闭程序时候,大概1s过后,命令行会接受到如下数据:
这是因为,我们关闭程序后,其定时器并未立刻取消,也就是说它在1s后仍然会发送数据给缓冲区,这样子命令行就会接收到这个数据。此时命令行就会搞得很懵了!!因此,我们需要改进一下:我们要把待机中的定时器给取消掉!
timer.c:
int timer_cancel(struct TIMER *timer)
{
int e;
struct TIMER *t;
e = io_load_eflags();
io_cli();//在设置过程中禁止改变定时器状态
if(timer->flags == TIMER_FLAGS_USING){//是否需要取消
if(timer == timerctl.t0){
// 第一个定时器的取消处理
t = timer->next_timer;
timerctl.t0 = t;
timerctl.next_time = t->timeout;
}else{
//非第一个定时器的处理,找到timer前一个定时器
t = timerctl.t0;
for(;;){
if(t->next_timer == timer){
break;
}
t = t->next_timer;
}
t->next_timer = timer->next_timer;//将timer的下一个指向timer的下一个
}
timer->flags = TIMER_FLAGS_ALLOC;
io_store_eflags(e);
return 1;//处理成功
}
io_store_eflags(e);
return 0;//不需要取消处理
}
void timer_cancelall(struct FIFO32 *fifo)
{
int e, i;
struct TIMER *t;
e = io_load_eflags();
io_cli();
for (i = 0; i < MAX_TIMER; i++) {
t = &timerctl.timers0[i];
if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) {
timer_cancel(t);
timer_free(t);
}
}
io_store_eflags(e);
return;
}
这里还要设置一个flags2用于启用自动取消功能:
struct TIMER
{
struct TIMER *next_timer;//指下一个定时器的地址
unsigned int timeout;// timeout的含义,这里指予定时刻,通过settime中赋予的超时时间+当前时刻来计算,当到达多少时间后算超时
char flags, flags2;//flags表示各个定时器的状态;flags2 代表是否开启自动取消
struct FIFO32 *fifo;//用于将超时的信息传给缓冲区。
int data;
};
然后,相对应的修改一下:
timer.c:
struct TIMER *timer_alloc(void)
{
int i;
for(i = 0; i < MAX_TIMER; i++)
{
if(timerctl.timers0[i].flags == 0)
{
timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
timerctl.timers0[i].flags2 = 0;
return &timerctl.timers0[i];
}
}
return 0;//没找到
}
console.c:
else if (edx == 16) {
reg[7] = (int) timer_alloc();
((struct TIMER *) reg[7])->flags2 = 1;//运行自动取消
}
下面就继续编写一个函数来进行取消:
cmd_app:
for(i = 0; i <MAX_SHEETS; i++){
sht = &(shtctl->sheets0[i]);
if((sht->flags & 0x11) == 0x11 && sht->task == task)
{
//找到被应用程序遗留的窗口
sheet_free(sht);//关闭
}
}
timer_cancelall(&task->fifo);
memman_free_4k(memman, (int) q, segsiz);
然后运行看看:
嘿嘿,成功!
总结
好了,今天到此为止了。有点劳累,好好休息,明天继续~😜