首页 > 编程语言 >java开发系统内核:caps 按键处理

java开发系统内核:caps 按键处理

时间:2023-06-14 11:05:40浏览次数:39  
标签:键盘 java shift caps cursor 内核 按键 data


更详细的讲解和代码调试演示过程,请参看视频
Linux kernel Hacker, 从零构建自己的内核

上一节,我们成功实现了对shift按键的处理,这一节,我们看看如何处理caps 按键,当该键按下时,输入系统的字符在大小写间切换。由于我们系统启动后,默认输入是大写字符,完成本节后,我们把系统的默认字符改成小写,按下caps键,系统字符自动切换成大写。

我们先看看完成后的运行结果,启动系统后,输入焦点在文本框中,没有按下caps键时,输入的字符如下:

java开发系统内核:caps 按键处理_java

然后点击tab键,将输入焦点切换到命令行控制台,然后再次点击cpas键,这时输入字符会切换成大写:

java开发系统内核:caps 按键处理_键盘处理_02

当caps键按下时,键盘给系统发过来的扫描码是0x3a, 当该键弹起后,键盘发送过来的断码为0xba, 因此,相应caps按键事件,只要针对这键盘中断发送过来的这两个数值进行对应处理就可。

当前我们键盘事件的分发逻辑是这样的,每次有按键事件产生时,运行CMain的主进程会被激活,该进程会从键盘数据队列中取出按键的扫描码或断码,然后判断,如果当前输入焦点在文本输入框,那么将按键的对应字符显示在文本框内。

如果输入焦点是命令行控制台,那么主进程会把键盘扫描码放入到控制台进程的输入队列,然后挂起自己,把cpu的控制权交给控制台进程。

实际上,并非所有按键的扫描码都需要全部传给控制台进程,对于一些特殊按键,例如shift, caps 等,这些按键主进程自己处理就可以了,不必要传给命令行控制台进程,所有我们添加一个判断按键是否是特殊按键的函数:

int isSpecialKey(int data) {
    transferScanCode(data);

    if (data == 0x3a || data == 0xba || data == 0x2a || data == 0x36
       || data == 0xaa || data == 0xb6) {
        return 1;
    }

    return 0;
}

当键盘发送过来的数值对应于shift按键的扫描码或断码时(0x2a, 0x36, 0xaa, 0xb6),或者是caps按键的扫描码或断码(0x3a, 0xa), 那么该函数返回1。

原来我们按键数据的分发存有问题,当按键按下后,键盘中断会被调用,键盘中断会把按键的扫描码发送到键盘数据缓存队列中,一旦键盘数据队列有数据进来后,它会激活主进程。主进程从数据中拿到键盘数据,然后判断当前输入焦点是否在其他进程的窗口,如果输入焦点在命令行控制台的话,主进程会把从队列中获得的键盘数据转发到命令行窗口的进程队列中,然后把自己挂起。

问题在于,按键会产生两个数据,一个是按下时发出的扫描码,另一个是按键弹起后发出的断码。所以处理一次按键事件时,必须同时处理扫描码和断码。但上面的机制导致,一旦按键按下后,主进程只把扫描码发送给目标进程就挂起了,断码没有发送,必须等到下次按键发生后,才能把上次按键的断码发送给目标进程,因而容易引起目标进程对键盘数据的解读错误。

如果大家尝试运行系统,把输入焦点切换到命令行,然后点击caps键,接着再点击其他按键,你就会发现字符无法正确的显示出来,这个现象就是上面所描述的问题导致的。

接下来,我们把所有按键的相应处理集中到一个函数中:

char  transferScanCode(int data) {
    if (data == 0x2a)  {//left shift key down
        key_shift |= 1;
    }

    if (data == 0x36) {
        //right shift key down 
        key_shift |= 2; 
    }

    if (data == 0xaa) {
        //left shift key up
        key_shift &= ~1;
    }

    if (data == 0xb6) {
       //right shift key up
        key_shift &= ~2;
    }

    //caps lock
    if (data == 0x3a) {
        if (caps_lock == 0) {
            caps_lock = 1;
        } else {
            caps_lock = 0;
        }
    }

    if (data == 0x2a || data == 0x36 || data == 0xaa || data == 0xb6 || 
        data >= 0x54 || data == 0x3a ) {
        return 0;
    }

    char c = 0;

    if (key_shift == 0 && data<0x54 && keytable[data] != 0) {
        c = keytable[data];
        if ('A' <= c && c <= 'Z' && caps_lock == 0) {
            c += 0x20;
        }

    } 
    else if (key_shift != 0 && data < 0x80 && keytable1[data] != 0){
        c = keytable1[data];
    }
    else  {
        c = 0;
    }

    return c;
}

如果caps键没有按下过,那么我们先把字符转换成小写然后在返还给窗口进程,如果按下过,那么直接把大写返回给窗口进程。因此窗口在显示字符前,直接调用该函数即可:

void CMain(void) {
    ...
    for(;;) {
    ....
    else if (key_to == 0) {
               if (transferScanCode(data) != 0 && cursor_x < 144) {
                   boxfill8(shtMsgBox->buf, shtMsgBox->bxsize, COL8_FFFFFF,cursor_x,
                   28, cursor_x + 7, 43);
                   sheet_refresh(shtctl, shtMsgBox, cursor_x, 28, cursor_x+8, 44);
                   char c = transferScanCode(data);
                   char buf[2] = {c, 0};
                   showString(shtctl,  shtMsgBox, cursor_x, 28, COL8_000000, buf);
                   cursor_x += 8;

                   stop_task_A = 1;

                   boxfill8(shtMsgBox->buf, shtMsgBox->bxsize, cursor_c, cursor_x,
                  28, cursor_x + 7, 43);
                  sheet_refresh(shtctl, shtMsgBox, cursor_x, 28, cursor_x+8, 44);
              } 
           } else if (isSpecialKey(data) == 0)  {

                 fifo8_put(&task_cons->fifo, data);
                 if (fifo8_status(&keyinfo) == 0) {             
                     task_sleep(task_a);
                 }
           }

       }
    ....
    }
    ...
}

上面代码的逻辑是,主进程获得键盘数据后,判断当前输入角度窗口,如果输入窗口是命令行控制台,那么判断当前按键是否是特殊字符,如果是,那么就没必要分发,如果不是,那么把队列中按键的所以数据,也就是扫描码和断码都分发给目标窗口的进程后,再把自己挂起。这么做,我们就能预防前面说过的问题。

命令行控制台窗口的字符显示,也需要调用transferScanCode来获取要显示的字符,代码修改如下:

void console_task(struct SHEET *sheet) {
....
    for(;;) {
    ....
    else {
             char c = transferScanCode(i);
             if (cursor_x < 240 && c!=0 ) {
                 boxfill8(sheet->buf, sheet->bxsize, 
                           COL8_000000, cursor_x,
                           28, cursor_x + 7, 43);
                 sheet_refresh(shtctl, sheet, cursor_x, 28, 
                 cursor_x+8, 44);

                 char s[2] = {c, 0};
                 showString(shtctl, sheet, cursor_x, 28, 
                 COL8_FFFFFF, s);
                 cursor_x += 8;
        }
    ....
    }
....
}

将上面的代码编译后,系统就可以切换大小写了。如果大家运行上面代码,点击caps键后,大小写确实能正常切换,但有个问题是,caps键点击后,它旁边的指示灯没有亮,如果要点亮指示灯,系统需要向键盘端口写入数据,要做到这点,又需要不少代码量,为了尽量降低代码的复杂性,我没有实现这点,有兴趣的同学可以自己尝试下。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:

java开发系统内核:caps 按键处理_操作系统_03


标签:键盘,java,shift,caps,cursor,内核,按键,data
From: https://blog.51cto.com/u_16160261/6476217

相关文章

  • java开发系统内核:像Linux一样使用中断实现内核API
    我们当前提供的内核API有个问题,就是每次使用时,需要计算API函数在内核中的位置,一旦内核代码改变,API接口的位置也会改变,同时调用API的应用程序也必须跟着改变,显然这种限制是不可接受的。为了突破当前缺陷,我们必须想出新的API提供办法。常用的做法是,仿照Linux将API当做一个中断调用,由......
  • java开发系统内核:使用C语言开发系统应用程序
    更详细的讲解和代码调试演示过程,请参看视频用java开发C语言编译器更详细的讲解和代码调试演示过程,请参看视频如何进入google,算法面试技能全面提升指南如果你对机器学习感兴趣,请参看一下链接:机器学习:神经网络导论更详细的讲解和代码调试演示过程,请参看视频LinuxkernelHacker,......
  • java开发C语言编译器:JVM 的基本操作指令介绍及其程序运行原理
    更详细的讲解和代码调试演示过程,请参看视频用java开发C语言编译器更详细的讲解和代码调试演示过程,请参看视频如何进入google,算法面试技能全面提升指南如果你对机器学习感兴趣,请参看一下链接:机器学习:神经网络导论更详细的讲解和代码调试演示过程,请参看视频LinuxkernelHacker,......
  • 编译原理动手实操,用java实现一个简易编译器-语法解析
    语法和解析树:举个例子看看,语法解析的过程。句子:“我看到刘德华唱歌”。在计算机里,怎么用程序解析它呢。从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是“我”,动词是“看见”,谓语从句是”刘德华唱歌“。因此一个句子可以分解成主语+动词+谓语从句:句子-->主语+动词+谓语......
  • java开发系统内核:进程的挂起和恢复
    有了进程的自动调度后,接下来的任务在于,如何将空闲进程挂起,空闲进程往往是那些没有具体任务需要处理的进程,因此,如果继续让其运行的话,那么必然会耗费宝贵的CPU资源,如果能让它先挂起,等到它需要执行具体任务时,再把它调度到前台,那才是一种合理的进程管理机制。我们实现的进程调度,是依赖......
  • java开发C语言编译器: return 语句的解释和执行
    在C语言程序中,很多函数并不是执行全部语句后,才从最底部返回的,大多数情况下,当某些条件成立时就可以通过return语句立即返回,而无需执行接下来的代码,本节,我们继续增强java开发的C语言解释器功能,使其能够处理return语句,完成本节代码后,我们的C语言解释器能够正常解析和执行下面的代码:in......
  • java开发系统内核:自动化进程切换
    我们已经通过时钟中断完成了两个进程间的相互切换。但当前实现有很大的缺陷,例如我们只能在两个指定的进程间切换,如果要想增添新的进程,那么,没增加一个进程,按照当前模式,我们只能再增加相应代码,这显然是不可接受的。因此,这节,我们希望完成进程的切换机制,使得有新进程时,我们无需改动代码......
  • java开发系统内核:实现进程自动切换,再现Linus当年辉煌一刻
    Linux操作系统内核于1991年10月5日被LinusBenedictTorvalds所开发,从此后,世界软件史揭开了新的帷幕,我们现在很多伟大的软件项目,都构建在Linux的基础之上,不说用于支撑谷歌,阿里,百度等巨头业务的后台大型服务器,现在风靡世界的安卓操作系统,也是构建在Linux之上的,可以说,没有当年Linux......
  • java实现C语言编译器:实现有参数的函数调用
    上一节,我们实现了没有参数传递的函数调用,本节,我们看看如何实现有参数传递的函数调用。有参数的函数调用要比无参数的函数调用复杂的多,一个难题在于,我们需要确定参数变量的作用域,例如下面的代码:inta;voidf(inta,intb){intc;c=a+b;}在代码里,有两个同名变量都......
  • 拼多多接口|api接口数据采集获取商品详情数据源代码Java演示
    ​拼多多提供了商品API,可以通过该API获取拼多多所有商品的详细信息,具体步骤如下: 申请开放平台接入。注册获取apikey和apisecret,调用API时需提供。调用拼多多API,获取商品详情。请求参数:参数说明通用参数说明version:API版本key:调用key,测试key:test_api_......