三十天自制操作系统(12)

第24天

我们已经可以让应用程序显示窗口了,如果一个应用程序显示了N个窗口,我们也应该让各个窗口可以现切换。实现鼠标切换难度比较大。我们简化为:如果按下f11那么将最下面的窗口放到最上面。

F11的按键编码是0x57。只要在task_a中判断键盘中断输入的数据为这个,就调用sheet_updown(shtctl->sheets[1], shtctl->top - 1)。最下面一层是桌面,最上面一层是鼠标。执行这个函数就可以将最下面的操口移到最上面。

要实现点击鼠标切换窗口基本的逻辑是这样。当鼠标点击画面的某个地方时,我们要按照从上到下的顺序,判断鼠标的位置落在哪个图层的范围内,并且还需要考虑该位置不是不透明的。

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;
      }
    }
  }
}

以上程序中mx,my分别表示鼠标所在屏幕上的x座标和y座标,相当于屏幕来说的,也就是说左上角为(0,0)。sht->vx0和sht->vy0表示该图层左上角相对于屏幕的座标。两个相减得到的x,y其实就是这个图层的相对座标,也就是说如果(x,y)=(0,0)就相当于在图层的左上角而不是屏幕的左上角了。

再实现窗口的移动功能。如果鼠标点击窗口的标题栏不放,移动鼠标那么窗口也跟着鼠标移动,直到鼠标放开。

首先明确窗口标题栏的位置,我们每次创建窗口标题栏左上角在距离窗口左右角(3,3)像素处,而标题栏右下角分别为(窗口右边-3, 21)处。

我们增加两个变量mmx和mmy,这两个变量初始值为-1。每次鼠标左键按下的时候判断该值是否为负数,如果为负数说明鼠标处理通常模式。当鼠标左键在标题栏按下时候保存当前鼠标x,y的坐标,这2个值也就变为正数了,鼠标处理窗口移动模式。当鼠标处于窗口称动模式时,每次移动鼠标都要调用sheet_slide(sht, sht->vx0 + x, sht->vy0 + y)函数移动窗口。

我们用这样的方法判断鼠标点击时到底是处于窗口的哪个位置,突然想起其实我们在显示窗口的时时候也画了个关闭窗口的按钮,只是一直没有用起来。我们可以用相同的方法判断鼠标点击是否在关闭窗口按钮的位置,如果在这个位置那么就关闭窗口。

我们之前已经实现了task_a窗口和console窗口的互相切换,使用了key_to变理,现在使用命令行也可以打开一个窗口,那么我们切换窗口的逻辑就要变化了。先实现tab切换窗口的功能:如果按下tab将键盘输入窗口切换到当前输入窗口的下一层,如果当前输入窗口是最下层那么换切到了上层。如果当前输入窗口关闭,那么当前输入窗口变为下一层。

用key_win这个变量存放当前处理输入模式的窗口地址。操作系统一开始是先运行task_a,产生一个窗口,然后打开console窗口,这里的key_win初始化为task_a的窗口。然后把之前使用key_to的地方全部修改为使用新变量。然后按下Tab时候的代码如下:

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);
}

上面两个keywin_off和keywin_on是控制窗口标题栏颜色和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); 
    }
  }
  return cur_c;
}

如果想要让鼠标点击窗口就把所点击的窗口切换为当前窗口也很简单。

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 (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);
      }
      if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {
        mmx = mx;   
        mmy = my;
      }
      if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
        if ((sht->flags & 0x10) != 0) {
          cons = (struct CONSOLE *) *((int *) 0x0fec);
          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;
    }
  }
}

这段程序跟之前比想差不多,首先从最上的图层开始一直往下找,直到点到鼠标点击的点所对应的图层,如果找到则把现在的图层key_off。再把找到的图层key_on就行了。

我们已经在操作系统层面实现了定时器的功能,但应用程序还不能调用,接下来我们写定时器的API,先设计API。

获取定时器(alloc)

  • edx = 16
  • eax = 定时器句柄(由操作系统返回)

设置定时器的发送数据(init)

  • edx = 17
  • ebx = 定时器的句柄
  • eax = 发送的数据

定时器的时间设定(set)

  • edx = 18
  • ebx = 定时器句柄
  • eax = 时间

释放定时器(free)

  • edx = 19
  • ebx = 定时器句柄

下面看代码:

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);
  }


_api_alloctimer:    ; int api_alloctimer(void);
  MOV       EDX,16
  INT       0x40
  RET


_api_inittimer:     ; void api_inittimer(int timer, int data);
  PUSH  EBX
  MOV       EDX,17
  MOV       EBX,[ESP+ 8]        ; timer
  MOV       EAX,[ESP+12]        ; data
  INT       0x40
  POP       EBX
  RET


_api_settimer:      ; void api_settimer(int timer, int time);
  PUSH  EBX
  MOV       EDX,18
  MOV       EBX,[ESP+ 8]        ; timer
  MOV       EAX,[ESP+12]        ; time
  INT       0x40
  POP       EBX
  RET


_api_freetimer:     ; void api_freetimer(int timer);
  PUSH  EBX
  MOV       EDX,19
  MOV       EBX,[ESP+ 8]        ; timer
  INT       0x40
  POP       EBX
  RET

现在来看应用程序怎么写

#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();

}

这段程序先的开一个窗口,然后设置定时器,超时时间为1秒,超时之后向消息队列发送数据为128,如果应用程序接收到的中断不是128说明用户按下了其他键,那就退出循环,程序结束。

这里有一个问题,如果已经设置了定时器但是在这1秒中间应用程序已经关闭了,那么这1秒的定时器超时之后还是会向消息队列发送128这个数据,那么console就会以为是一个键盘输入中断,会在窗口上打印一个乱码。

我们应该在应用程序关闭之后也把定时器给关掉。

先写一个关闭定时器的函数

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;
  timerctl.t0 = t;
  timerctl.next = t->timeout;
  } else {
    /* 非第一个定时器的取消处理 */
    /* 找到timer前一下定时器 */
    t = timerctl.t0;
    for (;;) {
      if (t->next == timer) {
        break;
      }
      t = t->next;
    }
    t->next = timer->next; 
  }
  timer->flags = TIMER_FLAGS_ALLOC;
  io_store_eflags(e);
  return 1; /* 取消处理成功 */
  }
  io_store_eflags(e);
    return 0; /* 不需要取消处理 */
}

实际一个应用程序可以设置任意多个定时器,所以当应用程序结束的时候应该关闭应用程序设置的所有定时器。为了方便我们给TIMER结构体中新设一个标志位,用来标志这个定时器是否在应用程序结束时也一起销毁。

else if (edx == 16) {
  reg[7] = (int) timer_alloc();
  ((struct TIMER *) reg[7])->flags2 = 1;    /* 允许自动取消 */

在api_allock中直接把这个标志位设置有1,表示允许自动取消。

再定一个函数用于取消所有函数

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;
}

然后在cmd_app函数中start_app这个函数调用结束之后再补上timer_cancell(&task->fifo)就可以了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 第26天 前一章中我们做的操作系统已经越来越像超作系统了,我们同时打开了2个console窗口,并且把task_a...
    whatcanhumando阅读 836评论 1 2
  • 第13天 在这之前的程序中每次新设置一个定时器都要创造一个队列与之对应,这样效率低而且导致操作系统主程序中逻辑复杂...
    whatcanhumando阅读 1,175评论 0 1
  • 第23天 操作系统的可执行文件中0x0000存放数据段的大小,0x0020位置存放malloc空间的起始地址这两个...
    whatcanhumando阅读 962评论 7 11
  • 第十天 内存管理中分配内存的函数虽然提供了以字节为单位进行内存分配的函数,但是也可能会造成频繁分配内存和释放内存造...
    whatcanhumando阅读 608评论 0 2