自制操作系统日志——第二十四天

本文详细记录了自制操作系统中窗口操作的实现过程,包括窗口切换、移动、鼠标关闭窗口以及用Tab键切换输入窗口的功能。此外,还介绍了定时器API的创建,包括分配、初始化、设定和释放。在遇到程序关闭后定时器仍发送数据的问题时,通过增加自动取消功能解决了这一问题。
摘要由CSDN通过智能技术生成

自制操作系统日志——第二十四天

今天呢,我们主要进行窗口的操作,诸如进行窗口切换,移动窗口,用鼠标切换窗口等等,下面让我们正式开始吧!



一、窗口切换

我们先来个简单的窗口切换,练练手吧!按下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);

然后运行看看:

在这里插入图片描述

嘿嘿,成功!


总结

好了,今天到此为止了。有点劳累,好好休息,明天继续~😜

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值